1. 程式人生 > >C# 深淺復制 MemberwiseClone(轉載)

C# 深淺復制 MemberwiseClone(轉載)

bubuko 技術分享 代碼 doc mar blank html 必須 性能

最近拜讀了大話設計模式:原型模式,該模式主要應用C# 深淺復制來實現的!關於深淺復制大家可參考MSDN:https://docs.microsoft.com/zh-cn/dotnet/api/system.object.memberwiseclone

所謂深淺復制可解讀為:

  • 淺復制:在C#中調用Object類的 MemberwiseClone() 方法即為淺復制。如果字段是值類型的,則對字段執行逐位復制,如果字段是引用類型的,則復制對象的引用,而不復制對象,因此:原始對象和其副本引用同一個對象!
  • 深復制:如果字段是值類型的,則對字段執行逐位復制,如果字段是引用類型的,則把引用類型的對象指向一個全新的對象!

上述的解釋可能看不太懂,我們作如下案例進行分析:

class Program
{
    public static void Main()
    {
        //創建P1對象
        Person p1 = new Person();
        p1.Age = 42;
        p1.Name = "Sam";
        p1.IdInfo = new IdInfo("081309207");

        //通過淺復制 得到P2對象
        Person p2 = p1.ShallowCopy();
        
//分別輸出 Console.WriteLine("對象P1相關屬性如下"); DisplayValues(p1); //p1.Name = ""; //p1.IdInfo.IdNumber = "XXXXX"; Console.WriteLine("對象P2相關屬性如下"); DisplayValues(p2); //現在測試深復制 Person p3 = p1.DeepCopy(); p1.Name = "George"; p1.Age
= 39; p1.IdInfo.IdNumber = "081309208"; Console.WriteLine("對象P1相關屬性如下"); DisplayValues(p1); //p1.IdInfo.IdNumber = "CCCCCCC"; Console.WriteLine("對象P3相關屬性如下"); DisplayValues(p3); Console.Read(); } public static void DisplayValues(Person p) { Console.WriteLine(" Name: {0:s}, Age: {1:d}", p.Name, p.Age); Console.WriteLine(" Value: {0:d}", p.IdInfo.IdNumber); } } public class IdInfo { public string IdNumber; public IdInfo(string IdNumber) { this.IdNumber = IdNumber; } } public class Person { public int Age; public string Name; public IdInfo IdInfo; public Person ShallowCopy() { return (Person)this.MemberwiseClone(); } public Person DeepCopy() { Person other = (Person)this.MemberwiseClone(); other.IdInfo = new IdInfo(IdInfo.IdNumber); other.Name = String.Copy(Name); return other; } }

上述代碼分析如下:
原始對象P1,通過淺復制得到對象P2,通過深復制得到P3
原始對象P1中的值類型屬性有:Age 和 Name ,引用類型對象有:IdInfo
根據上述淺復制的概念可知:P2中的Age 和 Name 相對於 P1是全新的,但P2中的 IdInfo 和 P1中的 IdInfo 是同一個對象,二者同在一個內存地址!
根據上述深復制的概念可知:P3中的Age 和 Name 相對於 P1是全新的,但P3中的 IdInfo 和 P1中的 IdInfo 不是同一個對象,也就是說 P3中的IdInfo是一個全新的對象,開辟了自己的內存地址!
上述代碼測試如下:

技術分享圖片

我們現在講代碼修改如下:

public static void Main()
{
    //創建P1對象
    Person p1 = new Person();
    p1.Age = 42;
    p1.Name = "Sam";
    p1.IdInfo = new IdInfo("081309207");

    //通過淺復制 得到P2對象
    Person p2 = p1.ShallowCopy();
    //分別輸出
    Console.WriteLine("對象P1相關屬性如下");
    DisplayValues(p1);
    p1.Name = "淺復制中,修改了P1的Name屬性,但Name是值類型,所以不會影響P2";
    p1.IdInfo.IdNumber = "淺復制中,修改了P1的IdInfo屬性,但IdInfo是引用類型,所以會影響P2 (淺復制中引用類型原始對象和副本指向同一內存地址)";
    Console.WriteLine("對象P2相關屬性如下");
    DisplayValues(p2);

    Console.Read();
}

在輸出P2之前,我們修改了P1對象的值類型Name 和 引用類型 IdInfo 。
無論是淺復制還是深復制,副本中的值類型都是全新的!
淺復制中原始對象和副本的引用類型指向同一內存地址,所以,修改了P1的IdInfo會同時影響P2的IdInfo
輸出如下:

技術分享圖片

繼續修改代碼,如下:

public static void Main()
{
    //創建P1對象
    Person p1 = new Person();
    p1.Age = 42;
    p1.Name = "Sam";
    p1.IdInfo = new IdInfo("081309207");


    //現在測試深復制
    Person p3 = p1.DeepCopy();

    p1.Name = "George";
    p1.Age = 39;
    p1.IdInfo.IdNumber = "081309208";
    Console.WriteLine("對象P1相關屬性如下");
    DisplayValues(p1);
    p1.IdInfo.IdNumber = "深復制中,修改了P1的IdInfo屬性,即使IdInfo是引用類型,也不會影響P3 (深復制中引用類型原始對象和副本分別指向不同的內存地址)";
    Console.WriteLine("對象P3相關屬性如下");
    DisplayValues(p3);
    Console.Read();
}

深復制中原始對象和副本的引用類型指向各自的地址,兩者完全是兩個不同的對象!
因此:修改P1不會影響P3
so,是不是很簡單,是不是很Easy.
深淺復制主要用於當創建一個對象需要消耗過多資源時,可以采取復制的方法提升效率!
大話設計模式的原話是這樣滴:當你New一個對象時,每New一次,都需要執行一個構造函數,如果構造函數的執行時間很長,那麽多次New對象時會大大拉低程序執行效率,因此:一般在初始化信息不發生變化的前提下克隆是最好的辦法,這既隱藏了對象的創建細節,又大大提升了性能
當然,如果每個類都要寫自己的深復制,這豈不是非常非常麻煩,因此,有一個通用的深復制方法,如下:

/// <summary>
/// 通用的深復制方法
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
public class BaseClone<T>
{
    public virtual T Clone()
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(memoryStream, this);
            memoryStream.Position = 0;
            return (T)formatter.Deserialize(memoryStream);
        }
    }
}

相關案例如下(通用的深復制方法使用時必須為相關類及類的引用類型加上可序列化標識:[Serializable]):

class Program
{
    public static void Main()
    {
        //創建P1對象
        Person p1 = new Person();
        p1.Age = 42;
        p1.Name = "Sam";
        p1.IdInfo = new IdInfo("081309207");


        //現在測試深復制
        Person p3 = p1.Clone();

        p1.Name = "George";
        p1.Age = 39;
        p1.IdInfo.IdNumber = "081309208";
        Console.WriteLine("對象P1相關屬性如下");
        DisplayValues(p1);
        p1.IdInfo.IdNumber = "深復制中,修改了P1的IdInfo屬性,即使IdInfo是引用類型,也不會影響P3 (深復制中引用類型原始對象和副本分別指向不同的內存地址)";
        Console.WriteLine("對象P3相關屬性如下");
        DisplayValues(p3);
        Console.Read();
    }

    public static void DisplayValues(Person p)
    {
        Console.WriteLine("      Name: {0:s}, Age: {1:d}", p.Name, p.Age);
        Console.WriteLine("      Value: {0:d}", p.IdInfo.IdNumber);
    }


}

[Serializable]
public class IdInfo
{
    public string IdNumber;

    public IdInfo(string IdNumber)
    {
        this.IdNumber = IdNumber;
    }
}

[Serializable]
public class Person : BaseClone<Person>
{
    public int Age;
    public string Name;

    public IdInfo IdInfo;

    public Person ShallowCopy()
    {
        return (Person)this.MemberwiseClone();
    }

    public Person DeepCopy()
    {
        Person other = (Person)this.MemberwiseClone();
        other.IdInfo = new IdInfo(IdInfo.IdNumber);
        other.Name = String.Copy(Name);
        return other;
    }

    public override Person Clone()
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(memoryStream, this);
            memoryStream.Position = 0;
            return (Person)formatter.Deserialize(memoryStream);
        }
    }
}

/// <summary>
/// 通用的深復制方法
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
public class BaseClone<T>
{
    public virtual T Clone()
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(memoryStream, this);
            memoryStream.Position = 0;
            return (T)formatter.Deserialize(memoryStream);
        }
    }
}

原文鏈接

C# 深淺復制 MemberwiseClone(轉載)