C#-深拷贝与浅拷贝

1.深拷贝与浅拷贝

  拷贝即是通常所说的复制(Copy)或克隆(Clone),对象的拷贝也就是从现有对象复制一个“一模一样”的新对象出来。虽然都是复制对象,但是不同的 复制方法,复制出来的新对象却并非完全一模一样,对象内部存在着一些差异。通常的拷贝方法有两种,即深拷贝和浅拷贝,那二者之间有何区别呢?MSDN里对 IClone接口的Clone方法有这样的说明:在深层副本中,所有的对象都是重复的;而在浅表副本中,只有顶级对象是重复的,并且顶级以下的对象包含引用。可以看出,深拷贝和浅拷贝之间的区别在于是否复制了子对象这如何理解呢?下面我通过带有子对象的代码来验证二者的区别。

首先定义两个类型:Student和ClassRoom,其中Student类型里包含ClassRoom,并使这两个类型都分别实现自定义的深拷贝接口(IDeepCopy)和浅拷贝接口(IShallowCopy)。

类图如下:

 

    /// <summary>
    /// 深拷贝接口
    /// </summary>
    interface IDeepCopy
    {
        object DeepCopy();
    }

    /// <summary>
    /// 浅拷贝接口
    /// </summary>
    interface IShallowCopy
    {
        object ShallowCopy();
    }

    /// <summary>
    /// 教室信息
    /// </summary>
    class ClassRoom : IDeepCopy, IShallowCopy
    {
        public int RoomID = 1;
        public string RoomName = "Room1";

        public override string ToString()
        {
            return "RoomID=" + RoomID + "\tRoomName=" + RoomName;
        }
        public object DeepCopy()
        {
            ClassRoom r = new ClassRoom();
            r.RoomID = this.RoomID;
            r.RoomName = this.RoomName;
            return r;
        }
        public object ShallowCopy()
        {
            //直接使用内置的浅拷贝方法返回
            return this.MemberwiseClone();
        }
    }

    class Student : IDeepCopy, IShallowCopy
    {
        //为了简化,使用public 字段
        public string Name;
        public int Age;
        //自定义类型,假设每个Student只拥有一个ClassRoom
        public ClassRoom Room = new ClassRoom();

        public Student()
        {
        }
        public Student(string name, int age)
        {
            this.Name = name;
            this.Age = age;
        }
        public object DeepCopy()
        {
            Student s = new Student();
            s.Name = this.Name;
            s.Age = this.Age;
            s.Room = (ClassRoom)this.Room.DeepCopy();
            return s;
        }
        public object ShallowCopy()
        {
            return this.MemberwiseClone();
        }

        public override string ToString()
        {
            return "Name:" + Name + "\tAge:" + Age + "\t" + Room.ToString();
        }

    }

3.C++拷贝构造函数

与其它面向对象语言不同,C++允许用户选择自定义对象的传递方式:值传递和引用传递。在值传递时就要使用对象拷贝,比如说按值传递参数,编译 器需要拷贝一个对象以避免原对象在函数体内被破坏。为此,C++提供了拷贝构造函数用来实现这种拷贝行为,拷贝构造函数是一种特殊的构造函数,用来完成一 些基于同一类的其它对象的构造和初始化。它唯一的参数是引用类型的,而且不可改变,通常的定义为X(const X&)。在拷贝构造函数里,用户可以定义对象的拷贝行为是深拷贝还是浅拷贝,如果用户没有实现自己的拷贝构造函数,那么编译器会提供一个默认实 现,该实现使用的是按位拷贝(bitwise copy),也即本文所说的浅拷贝。构造函数何时被调用呢?通常以下三种情况需要拷贝对象,此时拷贝构造函数将会被调用。
1.一个对象以值传递的方式传入函数体
2.一个对象以值传递的方式从函数返回
3.一个对象需要通过另外一个对象进行初始化

4.C# MemberwiseClone与ICloneable接口

和C++里的拷贝构造函数一样,C#也为每个对象提供了浅拷贝的默认实现,不过C#里没有拷贝构造函数,而是通过顶级类型Object里的 MemberwiseClone方法。MemberwiseClone 方法创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。有没有默认的深拷贝实现呢?当然是没有,因为需要所有参与拷贝 的对象定义自己的深拷贝行为。C++里需要用户实现拷贝构造函数,重写默认的浅拷贝;C#则不同,C#(确切的说是.NET Framework,而非C#语言)提供了ICloneable 接口,包含一个成员 Clone,它用于支持除 MemberwiseClone 所提供的克隆之外的克隆。C++通过拷贝构造函数无法确定子对象实现的是深拷贝还是浅拷贝,而C#在“强制”实现浅拷贝的基础上,提供 ICloneable 接口由用户定义深拷贝行为,通过接口来强制约束所有参与拷贝的对象,个人觉得,这也算是一小点C#对C++的改进。

 

5.深拷贝策略与实现

深拷贝的要点就是确保所有参与拷贝的对象都要提供自己的深拷贝实现,不管是C++拷贝构造函数还是C#的ICloneable 接口,事实上都是一种拷贝的约定。有了事先的约定,才能约束实现上的统一,所以关键在于设计。

但偶尔也会在后期才想到要深拷贝,怎么办?总不能修改所有之前的实现吧。有没有办法能够通过顶级类而不关心内部的子对象直接进行深拷贝呢?能不 能搞个万能的深拷贝方法,在想用的时候立即用,而不考虑前期的设计。这样“大包大揽”的方法,难点在于实现时必须自动获取子对象的信息,分别为子对象实现 深拷贝。C++里比较困难,.NET的反射机制使得实现容易一些。不过这样的方法虽然通用,实则破坏了封装,也不符合“每个类对自己负责”的设计原则。

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

public interface IAnimal
{
    void Behavior();
}

public interface ISpeak
{
    void Speak();
}

public interface IDeepCopy
{
    object DeepCopy();
}

public interface IShallowCopy
{
    object ShallowCopy();
}

class Dog : IAnimal
{
    public void Behavior()
    {
        Debug.Log("Dog wangwang");
    }
}

class Cat : IAnimal
{
    public void Behavior()
    {
        Debug.Log("Cat miaomiao");
    }
}

class Animal : IShallowCopy, IDeepCopy
{
    public float Temperature
    {
        get;
        set;
    }

    public Color Skin
    {
        get;
        set;
    }

    public int[] MyIntArray
    {
        get;
        set;
    }

    public Animal()
    {
        this.Temperature = 36.8f;
        this.Skin = Color.yellow;
    }

    public Animal(float temperature, Color skin)
    {
        this.Temperature = temperature;
        this.Skin = skin;
    }

    public object DeepCopy()
    {
        Animal a = new Animal(this.Temperature, this.Skin);
        return a;
    }

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

class Person : IAnimal, ISpeak, IDeepCopy, IShallowCopy, IComparable<Person>
{
    //枚举
    public enum MyEnum : int
    {
        red = 2,
        green = 1,
        blue = 9,
    }
    //const修饰的常量默认为static常量
    public const int myConstInt = 10;
    public readonly string name;

    private Color color;
    private double d;

    public Person(string name)
    {
        this.name = name;
    }

    public Person(string name, string sex, double d)
    {
        this.name = name;
        this.Sex = sex;
        this.d = d;
        this.animal = new Animal();
    }

    public void Behavior()
    {
        Debug.Log("Person HAHA");
    }

    public void Speak()
    {
        Debug.Log("I Can Speak");
    }

    //注意属性名后面不加括号,区分方法和属性
    public double MyDouble
    {
        get { return d; }
        set { d = 5.55; }
    }

    public Color MyColor
    {
        get;
        private set;
    }

    //自动属性,不用定义字段
    public string Sex
    {
        get;
        set;
    }

    public Animal animal
    {
        get;
        set;
    }

    public object DeepCopy()
    {
        Person p = new Person(this.name, this.Sex, this.MyDouble);
        p.animal = (Animal)this.animal.DeepCopy();
        return p;
    }

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

    //返回0是相等
    public int CompareTo(Person p)
    {
        if (p == null)
        {
            throw new ArgumentNullException("CompareTo(Person p)");  
        }

        //string内部CompareTo
        return this.name.CompareTo(p.name);
    }
}

public enum PersonCompareType
{
    PersonCompareTypeTemp,
    PersonCompareTypeSkin
}

//实现IComparer<T>接口, 自定义比较方式
class PersonCompare : IComparer<Person>
{
    private PersonCompareType type;

    public PersonCompare(PersonCompareType type)
    {
        this.type = type;
    }

    public int Compare(Person x, Person y)
    {
        if (x == null)
        {
            throw new ArgumentNullException("Compare(Person x)");
        }
        if (y == null)
        {
            throw new ArgumentNullException("Compare(Person y)");
        }

        switch (this.type)
        {
            case PersonCompareType.PersonCompareTypeTemp:
                return x.animal.Temperature.CompareTo(y.animal.Temperature);
            case PersonCompareType.PersonCompareTypeSkin:
                return 0;
            default:
                throw new ArgumentException("unexpected compare type");
        }
    }
}

public class TestDataStruct : MonoBehaviour {
    static readonly int A = B * 10;
    static readonly int B = 10;
    const int a = 10;

    private int p = 0;
	// Use this for initialization
	void Start () {
        //溢出产生编译时错误或导致引发 System.OverflowException
        //checked
        //{
        //    int a = 999999999;
        //    //a *= 66666;
        //}
        
        Debug.Log("A = " + A);
        Debug.Log("B = " + B);
        Person p = new Person("Jack");
        //p.name = "Marry";
        Debug.Log("p.name = " + p.name);
        Debug.Log("myConstInt = " + Person.myConstInt);

        foreach (var name in Enum.GetNames(typeof(Person.MyEnum)))
        {
            Debug.Log("name = " + name);
        }
        foreach (var value in Enum.GetValues(typeof(Person.MyEnum)))
        {
            Debug.Log("value = " + (int)value);
        }
        Debug.Log("parseName = " + (Person.MyEnum)Enum.Parse(typeof(Person.MyEnum), "9"));
        if (Enum.IsDefined(typeof(Person.MyEnum), "black"))
        {
            Debug.Log("parseValue = " + "NOT NULL");
        }
        else
        {
            Debug.Log("parseValue = " + "NULL");
        }
        Dog dog = new Dog();
        Behavior(dog);
        Cat cat = new Cat();
        Behavior(cat);
        Behavior(p);
        p.Speak();

        Debug.Log(p.MyDouble);
        p.MyDouble = 6.66;
        Debug.Log(p.MyDouble);

        //p.MyColor = Color.red;
        Debug.Log(p.MyColor);

        p.Sex = "man";
        //Debug.Log(p.Sex);

        Array array = Array.CreateInstance(typeof(int), 5);
        for (int i = 0; i < array.Length; i++)
        {
            array.SetValue(33, i);
        }
        for (int i = 0; i < array.Length; i++)
        {
            Debug.Log(array.GetValue(i));
        }

        int[] myArr = new int[] { 1, 2, 3, 4, 5 };


        TestClone();
        TestArraySeg();
	}

    private void TestClone()
    {
        Person[] ps = new Person[]{
            new Person("Jane", "women", 1.1),
            new Person("Lily", "women", 1.2),
            new Person("Bob", "man", 1.3)
        };

        //深拷贝和浅拷贝之间的区别在于是否复制了子对象
        //Clone()发生了浅拷贝
        //Person[] psClone = (Person[])ps.Clone();
        //psClone[0].Sex = "ppp";
        //psClone[0].MyDouble = 6.6666;

        //Copy属于浅拷贝
        //Person[] psClone = new Person[3];
        //Array.Copy(ps, psClone, 3);

        Person[] psClone = new Person[3];
        for (int i = 0; i < ps.Length; i++)
        {
            //psClone[i] = (Person)ps[i].ShallowCopy();
            psClone[i] = (Person)ps[i].DeepCopy();
        }

        //animal是p的子对象
        psClone[0].Sex = "ppp";
        psClone[0].MyDouble = 6.6666;
        psClone[0].animal.Skin = Color.white;
        psClone[0].animal.Temperature = 50.0f;

        for (int i = 0; i < ps.Length; i++)
        {
            Debug.Log(ps[i].name + " " + ps[i].Sex + " " + ps[i].MyDouble + " " + ps[i].animal.Skin + " " + ps[i].animal.Temperature);
        }

        for (int i = 0; i < psClone.Length; i++)
        {
            Debug.Log(psClone[i].name + " " + psClone[i].Sex + " " + psClone[i].MyDouble + " " + psClone[i].animal.Skin + " " + psClone[i].animal.Temperature);
        }

        //自定义类Array排序需要实现IComparable接口
        //Array.Sort(ps);
        for (int i = 0; i < ps.Length; i++)
        {
            Debug.Log(ps[i].name + " " + ps[i].Sex + " " + ps[i].MyDouble + " " + ps[i].animal.Skin + " " + ps[i].animal.Temperature);
        }
        Debug.Log("===");
        Array.Sort(psClone, new PersonCompare(PersonCompareType.PersonCompareTypeTemp));
        for (int i = 0; i < ps.Length; i++)
        {
            Debug.Log(psClone[i].name + " " + psClone[i].Sex + " " + psClone[i].MyDouble + " " + psClone[i].animal.Skin + " " + psClone[i].animal.Temperature);
        }
    }

    //数组段不复制原数组的元素,但原数组可以通过ArraySegment<T>访问。如果数组段中的元素改变了,这些变化就会反映到原数组中。
    private void TestArraySeg()
    {
        int[] a1 = new int[] { 1, 3, 5, 7, 9 };
        int[] a2 = new int[] { 2, 4, 6, 8, 10 };

        var seg = new ArraySegment<int>[2]
        {
            new ArraySegment<int>(a1, 0, 3),
            new ArraySegment<int>(a2, 2, 3)
        };

        Debug.Log("SumArraySegment = " + SumArraySegment(seg));

        for (int i = 0; i < a1.Length; i++)
        {
            Debug.Log("a1 = " + a1[i]);
        }

        for (int i = 0; i < a2.Length; i++)
        {
            Debug.Log("a2 = " + a2[i]);
        }
    }

    private int SumArraySegment(ArraySegment<int>[] seg)
    {
        int sum = 0;
        for (int i = 0; i < seg.Length; i++)
        {
            for (int j = seg[i].Offset; j < seg[i].Offset + seg[i].Count; j++)
            {
                //可以通过数组段访问原数组,修改原数组的值
                seg[i].Array[j] = 0;
                //sum += seg[i].Array[j];
            }
        }
        return sum;
    }

    //接口作为参数 调用实际对象的方法
    private void Behavior(IAnimal myIanimal) 
    { 
        myIanimal.Behavior(); 
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值