C# 中的 淺表拷貝和深層拷貝


    淺表拷貝得到一個新的實例,一個與原始對象類型相同、值類型字段相同的拷貝。但是,如果字段是引用類型的,則拷貝的是該引用, 而不是的對象。若想將引用字段的對象也拷貝過去,則稱為深拷貝。

1.System.Object提供了受保護的方法 MemberwiseClone,可用來實現“淺表”拷貝。由於該方法標記為“受保護”級別,因此,我們只能在繼承類或該類內部才能訪問該方法:

        //淺復制
        public Student ShallowClone()
        {
            return this.MemberwiseClone() as Student;
        }

2.使用序列化與反序列化的方式,這種方式雖可實現深度拷貝,但在外面引用時一定要記得關閉所創建的MemoryStream流:(類可序列化的條件:1.為Public,2.增加Serializable屬性標記類)

using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

//深拷貝  
        public Student DeepClone()
        {
            using (Stream objectStream = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(objectStream, this);
                objectStream.Seek(0, SeekOrigin.Begin);
                return formatter.Deserialize(objectStream) as Student;
            }
      

3.在一個外國人寫的博客中(http://www.codeproject.com/Articles/3441/Base-class-for-cloning-an-object-in-C),使用反射的方法來解決了這個問題。他寫了一個BaseObject類,如果我們繼承這個類就可以實現深度拷貝,下面是他的實現方法:

創建一個實現 ICloneable 接口的有默認行為的抽象類,所謂的默認行為就是使用以下庫函數來拷貝類里的每一個字段。

1.遍歷類里的每個字段,看看是否支持ICloneable接口。

2.如果不支持ICloneable接口,按下面規則進行設置,也就是說如果字段是個值類型,那將其拷貝,如果是引用類型則拷貝字段指向通一個對象。

3.如果支持ICloneable,在科隆對象中使用它的克隆方法進行設置。

4.如果字段支持IEnumerable接口,需要看看它是否支持IList或者IDirectionary接口,如果支持,迭代集合看看是否支持ICloneable接口。

我們所要做的就是使得所有字段支持ICloneable接口。

BaseObject的實現:


///
<summary> /// BaseObject class is an abstract class for you to derive from. /// Every class that will be dirived from this class will support the /// Clone method automaticly.<br> /// The class implements the interface ICloneable and there /// for every object that will be derived <br> /// from this object will support the ICloneable interface as well. /// </summary> using System.Reflection;
using System.Collections;
public abstract class BaseObject : ICloneable { /// <summary> /// Clone the object, and returning a reference to a cloned object. /// </summary> /// <returns>Reference to the new cloned /// object.</returns> public object Clone() { //First we create an instance of this specific type. object newObject = Activator.CreateInstance( this.GetType() ); //We get the array of fields for the new type instance. FieldInfo[] fields = newObject.GetType().GetFields(); int i = 0; foreach( FieldInfo fi in this.GetType().GetFields() ) { //We query if the fiels support the ICloneable interface. Type ICloneType = fi.FieldType. GetInterface( "ICloneable" , true ); if( ICloneType != null ) { //Getting the ICloneable interface from the object. ICloneable IClone = (ICloneable)fi.GetValue(this); //We use the clone method to set the new value to the field. fields[i].SetValue( newObject , IClone.Clone() ); } else { // If the field doesn't support the ICloneable // interface then just set it. fields[i].SetValue( newObject , fi.GetValue(this) ); } //Now we check if the object support the //IEnumerable interface, so if it does //we need to enumerate all its items and check if //they support the ICloneable interface. Type IEnumerableType = fi.FieldType.GetInterface ( "IEnumerable" , true ); if( IEnumerableType != null ) { //Get the IEnumerable interface from the field. IEnumerable IEnum = (IEnumerable)fi.GetValue(this); //This version support the IList and the //IDictionary interfaces to iterate on collections. Type IListType = fields[i].FieldType.GetInterface ( "IList" , true ); Type IDicType = fields[i].FieldType.GetInterface ( "IDictionary" , true ); int j = 0; if( IListType != null ) { //Getting the IList interface. IList list = (IList)fields[i].GetValue(newObject); foreach( object obj in IEnum ) { //Checking to see if the current item //support the ICloneable interface. ICloneType = obj.GetType(). GetInterface( "ICloneable" , true ); if( ICloneType != null ) { //If it does support the ICloneable interface, //we use it to set the clone of //the object in the list. ICloneable clone = (ICloneable)obj; list[j] = clone.Clone(); } //NOTE: If the item in the list is not //support the ICloneable interface then in the //cloned list this item will be the same //item as in the original list //(as long as this type is a reference type). j++; } } else if( IDicType != null ) { //Getting the dictionary interface. IDictionary dic = (IDictionary)fields[i]. GetValue(newObject); j = 0; foreach( DictionaryEntry de in IEnum ) { //Checking to see if the item //support the ICloneable interface. ICloneType = de.Value.GetType(). GetInterface( "ICloneable" , true ); if( ICloneType != null ) { ICloneable clone = (ICloneable)de.Value; dic[de.Key] = clone.Clone(); } j++; } } } i++; } return newObject; } }

 寫了個例子,試了下上面第3種方式,我發現如果類中含有Array類型的成員變量,是可以深度復制的。但如果含有List成員變量,List成員還是淺復制(並且會報原始數據被修改)。原因是Array繼承了ICloneable,但List沒有繼承,BaseObject中的實現方法就會把List成員變量當成值類型的變量直接賦值。我的解決辦法是創建MyList類,實現Clone方法:

    [Serializable] 
    public class MyList<T> : List<T>,ICloneable
    {
        //clone一個新的List
        public object Clone()
        {
            MyList<T> newList = new MyList<T>();
            foreach(T item in this){      //分別創建當中的每個成員對象
                newList.Add(Activator.CreateInstance<T>());
            }
            return newList;
        }
    }
    [Serializable] 
    public class Student:BaseObject
    {
        public int Age;
        public string Name;
        public MyList<Pet> PetList;

        //淺復制
        public Student ShallowClone()
        {
            return this.MemberwiseClone() as Student;
        }

        //深拷貝  
        public Student DeepClone()
        {
            using (Stream objectStream = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(objectStream, this);   //序列
                objectStream.Seek(0, SeekOrigin.Begin);
                return formatter.Deserialize(objectStream) as Student;//反序列
            }
        }

        public string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("Age:{0}, Name:{1}", this.Age, this.Name);
            sb.AppendLine();
            foreach (Pet p in this.PetList)
            {
                sb.AppendFormat("PetName:{0}, Weight:{1}", p.Name, p.Weight);
                sb.AppendLine();
            }
            return sb.ToString();
        }
    }
    [Serializable]
    public class Pet : BaseObject
    {
        public string Name;
        public int Weight;
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyList<Pet> pets1 = new MyList<Pet>();
            pets1.Add(new Pet { Weight=60,Name="pp"});
            pets1.Add(new Pet { Weight =80, Name = "bb" });
            Student stu1 = new Student
            {
                Age = 15,
                Name = "ZZW",
                PetList = pets1
            };
            Student stu2 = (Student)stu1.Clone();
            Console.WriteLine("Before modidfy.....");
            Console.WriteLine("Stu1:" + stu1.ToString());
            Console.WriteLine("Stu2:" + stu2.ToString());
            stu2.Age = 66;
            stu2.Name = "jjj";
            foreach (Pet p in stu2.PetList)
            {
                p.Name = "xx";
                p.Weight = 100;
            }
            Console.WriteLine("After Stu2 modidfy.....");
            Console.WriteLine("Stu1:" + stu1.ToString());
            Console.WriteLine("Stu2:" + stu2.ToString());
            Console.ReadKey();
        }
    }

輸出結果:

Before modidfy.....
Stu1:Age:15, Name:ZZW
PetName:pp, Weight:60
PetName:bb, Weight:80

Stu2:Age:15, Name:ZZW
PetName:pp, Weight:60
PetName:bb, Weight:80

After Stu2 modidfy.....
Stu1:Age:15, Name:ZZW
PetName:pp, Weight:60
PetName:bb, Weight:80

Stu2:Age:66, Name:jjj
PetName:xx, Weight:100
PetName:xx, Weight:100

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM