程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C# Tips-淺拷貝和深拷貝(shallow copy VS deep copy )

C# Tips-淺拷貝和深拷貝(shallow copy VS deep copy )

編輯:關於C#

引言

C#中有兩種類型變量,一種 是值類型變量,一種是引用類型變量,對於值類型變量,深拷貝和前拷貝都是通過賦值操作符號(=)實現,其效果一致,將對象中的值類型的字段拷貝到新的對象中.這個很容易理解。 本文重點討論引用類型變量的拷貝機制和實現。

C#中引用類型對象的copy操作有兩種:

淺拷貝(影子克隆/shallow copy):只復制對象的值類型字段,對象的引用類型,仍屬於原來的引用.

深拷貝(深度克隆):不僅復制對象的值類型字段,同時也復制原對象中的對象.就是說完全是新對象產生的.

淺拷貝和深拷貝之間的區別:淺拷貝是指將對象中的數值類型的字段拷貝到新的對象中,而對象中的引用型字段則指復制它的一個引用到目標對象。

注意:string類型有點特殊,對於淺拷貝,類值類型對象進行處理。

淺拷貝的實現

使用Object類MemberwiseClone實現

MemberwiseClone:創建當前 Object 的淺表副本。

MemberwiseClone 方法創建一個淺表副本,方法是創建一個新對象,然後將當前對象的非靜態字段復制到該新對象。如果字段是值類型的,則對該字段執行逐位復制。如果字段是引用類型,則復制引用但不復制引用的對象;因此,原始對象及其復本引用同一對象。

代碼實現如下:

 public class Person
    {
        public int Age { get; set; }
        public string Address { get; set; }
        public Name Name { get; set; }

        public object Clone()
        {
           return   this.MemberwiseClone();
        }

    }

    public class Name
    {
        public Name(string frisName,string lastName)
        {
            FristName = frisName;
            LastName = lastName;
        }
        public string FristName { get; set; }
        public string LastName { get; set; }
    }

賦值操作(=)VS使用Object類MemberwiseClone實現

對於引用類型的變量,我們有種誤解,認為賦值操作就是淺拷貝一種,其實不然,兩者有區別。

淺拷貝(shallow copy)對於引用類型對象中的值類型字段進行了逐位復制。賦值運算符只是把源對象的引用賦值給目的對象,兩者引用同一個對象。

淺拷貝後的對象的值類型字段更改不會反映到源對象,而賦值運算後的對象的值類型字段更改會反映到源對象

代碼實現如下:

    public class Person
    {
        public int Age { get; set; }
        public string Address { get; set; }
        public Name Name { get; set; }
    }

    public class Name
    {
        public Name(string frisName,string lastName)
        {
            FristName = frisName;
            LastName = lastName;
        }
        public string FristName { get; set; }
        public string LastName { get; set; }
    }

深拷貝實現

相對於淺拷貝,是指依照源對象為原型,創建一個新對象,將當前對象的所有字段進行執行逐位復制並支持遞歸,不管是是值類型還是引用類型,不管是靜態字段還是非靜態字段。

在C#中,我們們有三種方法實現深拷貝

實現ICloneable接口,自定義拷貝功能。

ICloneable 接口,支持克隆,即用與現有實例相同的值創建類的新實例。

ICloneable 接口包含一個成員 Clone,它用於支持除 MemberwiseClone 所提供的克隆之外的克隆。Clone 既可作為深層副本實現,也可作為淺表副本實現。在深層副本中,所有的對象都是重復的;而在淺表副本中,只有頂級對象是重復的,並且頂級以下的對象包含引用。 結果克隆必須與原始實例具有相同的類型或是原始實例的兼容類型。

代碼實現如下:

 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;
        }
    }

    public class Name
    {
        public Name(string frisName, string lastName)
        {
            FristName = frisName;
            LastName = lastName;
        }
        public string FristName { get; set; }
        public string LastName { get; set; }
    }

大家可以看到,Person類繼承了接口ICloneable並手動實現了其Clone方法,這是個簡單的類,試想一下,如果你的類有成千上萬個引用類型成員(當然太誇張,幾十個還是有的),這是不是份很恐怖的勞力活?

序列化/反序列化類實現

不知道你有沒有注意到DataSet對象,對於他提供的兩個方法:

DataSet.Clone 方法,復制 DataSet 的結構,包括所有 DataTable 架構、關系和約束。不要復制任何數據。

新 DataSet,其架構與當前 DataSet 的架構相同,但是不包含任何數據。注意 如果已創建這些類的子類,則復本也將屬於相同的子類。

DataSet.Copy 方法復制該 DataSet 的結構和數據.

新的 DataSet,具有與該 DataSet 相同的結構(表架構、關系和約束)和數據。注意如果已創建這些類的子類,則副本也將屬於相同的子類。

好像既不是淺拷貝,又不是深拷貝,是不是很失望?但是兩個結合起來不是我們要的深拷貝嗎?看看DataSet的實現,注意序列化接口:ISerializable

序列化是將對象或對象圖形轉換為線性字節序列,以存儲或傳輸到另一個位置的過程。反序列化是接受存儲的信息並利用它重新創建對象的過程。

通過 ISerializable 接口,類可以執行其自己的序列化行為。

轉換為線性字節序列後並利用其重新創建對象的過程是不是和我們的深拷貝的語意“逐位復制”很相像?

代碼實現如下:

  [Serializable]
    public class Person : ICloneable
    {
        public int Age { get; set; }
        public string Address { get; set; }
        public Name Name { get; set; }

        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]
    public class Name
    {
        public Name(string frisName, string lastName)
        {
            FristName = frisName;
            LastName = lastName;
        }
        public string FristName { get; set; }
        public string LastName { get; set; }
    }

}

注意:通過序列化和反序列化實現深拷貝,其和其字段類型必須標記為可序列化類型,既添加特性(Attribute)[Serializable]。

通過反射實現

通過序列化/反序列化方式我們能比較流暢的實現深拷貝,但是涉及到IO操作,托管的的環境中,IO操作比較消耗資源。 能不能有更優雅的解決方案。CreateInstance,對,利用反射特性。這個方法大家可以參考這篇博客:http://rubenhak.com/?p=70 文章反射類的Attribute,利用Activator.CreateInstance New一個類出來(有點像DataSet.Clone先獲得架構),然後利用PropertyInfo的SetValue和GetValue方法,遍歷的方式進行值填充。

代碼實現如下:

public class Person
{
    private List<Person> _friends = new List<Person>();

    public string Firstname { get; set; }
    public string Lastname { get; set; }

    [Cloneable(CloneableState.Exclude)]
    [Cloneable(CloneableState.Include, "Friends")]
    public List<Person> Friends { get { return _friends; } }

    [Cloneable(CloneableState.Exclude)]
    public PersonManager Manager { get; set; }
}

C#為什麼要設計深拷貝和淺拷貝?

這個我也一直也找不到一個合適的答案,希望有人來討論下!

出處:http://www.cnblogs.com/Roping/archive/2009/05/13/1455880.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved