[C#ステップアップシリーズ]特集1:深いコピーと浅いコピーを深く解析する


一、前言
今週は面接に参加して、面接で深いコピーの違いを聞いて、それから私は簡単にそれらの違いを話して、それから面接官はまた質問を続けて、どのように深いコピーを実現しますか?反射を使って、逆シーケンス化やツリーの表現もできると面接官が提示した.そしてまた,反射で深いコピーを実現すると,相互参照対象の問題をどのように解決するかを尋ね続けた.その时、私が答えたのは、反射して実現する必要はありません.反シーケンス化で実現するか、2つのオブジェクトを相互に引用することを直接避けるかということです.そして面接官は、必ず反射で書くとしたら、どうやってこの問題を解決しますか?この時私は呆然とした.
これでこの文章ができた.今日は深浅コピーの問題を深く解析します.
二、深いコピーVs浅いコピー
まず、深浅コピーといえば、自然に問題がありますか?深いコピーとは何か、浅いコピーとは何か.以下、それらの定義について具体的に説明します.
深度コピー:オブジェクトをコピーする場合、オブジェクトの参照だけでなく、そのオブジェクトの参照値も一緒にコピーします.これにより、コピー後のコピーオブジェクトはソースオブジェクトとは独立しており、いずれのオブジェクトの変更も他のオブジェクトに影響を与えません.例えば、一人を張三と呼び、クローン技術を使って張三をクローンし、もう一人を李四と呼ぶ.このように張三と李四は互いに独立しており、張三が腕が欠けていても李四が足が少なくても他の人に影響を与えない.はい.NET領域では,int,Double,構造体,列挙など,値オブジェクトが典型的な例である.具体例は以下の通りです.
int source = 123;
//             
int copy = source;
//                   
copy = 234;
//                    
source = 345;

≪浅いコピー|Simple Copy|oem_src≫:オブジェクトをコピーする場合、オブジェクトの参照のみをコピーしてコピーしますが、コピー・オブジェクトとソース・オブジェクトは同じエンティティを参照します.この場合、いずれかのオブジェクトの変更が別のオブジェクトに影響します.例えば、一人で最初は張三と呼んで、それから名前を張老三に変えましたが、彼らはまだ同じ人で、腕が欠けていても張老三が足が少なくても、同じ人に反応しています.はい.NETでの参照タイプが一例です.クラスタイプなどです.具体例は以下の通りです.
public class Person
    {
        public string Name { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Person sourceP = new Person() { Name = "  " };
            Person copyP = sourceP; //    
            copyP.Name = "   "; //       Name 
            //     "   ",         ,                 
            Console.WriteLine("Person.Name: [SourceP: {0}] [CopyP:{1}]", sourceP.Name, copyP.Name);
            Console.Read();
        }
    }

三、深浅コピーのいくつかの実現方式
深浅コピーの定義はすでに理解されており,彼らの違いも定義に現れている.それらの定義と違いを紹介した後、自然にどのように実現するのでしょうか.
浅いコピーの実現方式は簡単である.NET自身も実現を提供している.すべてのオブジェクトの親がSystemであることを知っています.Objectオブジェクト、この親オブジェクトには、浅いコピーを実現するために使用できるMemberwiseCloneメソッドがあります.次に、浅いコピーの実現方法を具体的に見てみましょう.具体的なプレゼンテーションコードは以下の通りです.
//   ICloneable  ,   Clone  
    class ShallowCopyDemoClass : ICloneable
    {
        public int intValue = 1;
        public string strValue = "1";
        public PersonEnum pEnum = PersonEnum.EnumA;
        public PersonStruct pStruct = new PersonStruct() {  StructValue = 1};
        public Person pClass = new Person("1");
        public int[] pIntArray = new int[] { 1 };
        public string[] pStringArray = new string[] { "1" };

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

        #endregion 

    }

    class Person
    {
        public string Name;
        public Person(string name)
        {
            Name = name;
        }
    }

    public enum PersonEnum
    {
        EnumA = 0,
        EnumB = 1
    }

    public struct PersonStruct
    {
        public int StructValue;
    }

上記のクラスでは,ObjectのMemberwiseCloneメソッドを直接呼び出して浅いコピーを完了させるためのIConeableインタフェースのCloneメソッドを書き換え,深いコピーを実現したい場合は,Cloneメソッドで深いコピーの論理を実現することもできる.次に、上記で定義したクラスの浅いコピーテストを行い、実装された浅いコピーであるかどうかを確認します.具体的なプレゼンテーションコードは次のとおりです.
class Program
    {
        static void Main(string[] args)
        {
            ShallowCopyDemo();
            // List      
            ListShallowCopyDemo();
        }

        public static void ListShallowCopyDemo()
        {
            List personList = new List() 
            {
                new PersonA() { Name="PersonA", Age= 10, ClassA= new A() { TestProperty = "AProperty"} },
                new PersonA() { Name="PersonA2", Age= 20, ClassA= new A() { TestProperty = "AProperty2"} }
            };
            //   2           
            List personsCopy = new List(personList);
            PersonA[] personCopy2 = new PersonA[2];
            personList.CopyTo(personCopy2);

       //          ,          ,  2           ,              ,              
            personsCopy.First().ClassA.TestProperty = "AProperty3";
            WriteLog(string.Format("personCopy2.First().ClassA.TestProperty is {0}", personCopy2.First().ClassA.TestProperty));
            WriteLog(string.Format("personList.First().ClassA.TestProperty is {0}", personList.First().ClassA.TestProperty));
            WriteLog(string.Format("personsCopy.First().ClassA.TestProperty is {0}", personsCopy.First().ClassA.TestProperty));
       Console.Read(); 
        }

        public static void ShallowCopyDemo()
        {
            ShallowCopyDemoClass DemoA = new ShallowCopyDemoClass();
            ShallowCopyDemoClass DemoB = DemoA.Clone() as ShallowCopyDemoClass ;
            DemoB.intValue = 2;
            WriteLog(string.Format("    int->[A:{0}] [B:{1}]", DemoA.intValue, DemoB.intValue));
            DemoB.strValue = "2";
            WriteLog(string.Format("    string->[A:{0}] [B:{1}]", DemoA.strValue, DemoB.strValue));
            DemoB.pEnum = PersonEnum.EnumB;
            WriteLog(string.Format("  Enum->[A: {0}] [B:{1}]", DemoA.pEnum, DemoB.pEnum));
            DemoB.pStruct.StructValue = 2;
            WriteLog(string.Format("    struct->[A: {0}] [B: {1}]", DemoA.pStruct.StructValue, DemoB.pStruct.StructValue));
            DemoB.pIntArray[0] = 2;
            WriteLog(string.Format("   intArray->[A:{0}] [B:{1}]", DemoA.pIntArray[0], DemoB.pIntArray[0]));
            DemoB.pStringArray[0] = "2";
            WriteLog(string.Format("stringArray->[A:{0}] [B:{1}]", DemoA.pStringArray[0], DemoB.pStringArray[0]));
            DemoB.pClass.Name = "2";
            WriteLog(string.Format("      Class->[A:{0}] [B:{1}]", DemoA.pClass.Name, DemoB.pClass.Name));
       Console.WriteLine();
      } 
private static void WriteLog(string msg) { Console.WriteLine(msg); }   } }

上のコードの実行結果を下図に示します.
以上の運転結果から分かるように、NETの値タイプはデフォルトでは深コピーされ、参照タイプではデフォルトでは浅いコピーが実現されます.したがって、クラス内の参照タイプのプロパティが変更されると、別のオブジェクトも変更されます.
浅いコピーの実現方法について説明しましたが、深いコピーはどのように実現しますか?前のセクションでは、反射、逆シーケンス化、式ツリーなど、深いコピーを実現する方法について説明しました.ここでは、反射と逆シーケンス化の方法だけを紹介しますが、式ツリーの方法はネット上でも見つかりませんでした.その時、面接官はいいと言っていましたが、式ツリーの実現方法を見つけたら、伝言を残してください.まず、反射の実現方法を見てみましょう.
//          
        public static T DeepCopyWithReflection(T obj)
        {
            Type type = obj.GetType();

            //                
            if (obj is string || type.IsValueType) return obj;

            if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);
                for (int i = 0; i  
  

   , 3 , :

//   XML          
        public static T DeepCopyWithXmlSerializer(T obj)
        {
            object retval;
            using (MemoryStream ms = new MemoryStream())
            {
                XmlSerializer xml = new XmlSerializer(typeof(T));
                xml.Serialize(ms, obj);
                ms.Seek(0, SeekOrigin.Begin);
                retval = xml.Deserialize(ms);
                ms.Close();
            }

            return (T)retval;
        }

        //               
        public static T DeepCopyWithBinarySerialize(T obj)
        {
            object retval;
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                //      
                bf.Serialize(ms, obj);
                ms.Seek(0, SeekOrigin.Begin);
                //        
                retval = bf.Deserialize(ms);
                ms.Close();
            }

            return (T)retval;
        }

        //   DataContractSerializer          
        public static T DeepCopy(T obj)
        {
            object retval;
            using (MemoryStream ms = new MemoryStream())
            {
                DataContractSerializer ser = new DataContractSerializer(typeof(T));
                ser.WriteObject(ms, obj);
                ms.Seek(0, SeekOrigin.Begin);
                retval = ser.ReadObject(ms);
                ms.Close();
            }
            return (T)retval;
        }
        
        //       
        // ....

、 による いコピーは の をどのように するか
の の では、 オブジェクトに してStackOverflowerのエラーが し、オブジェクトの によりメソッドがループ び しされます. に、 オブジェクトの を します.
[Serializable]
    public class DeepCopyDemoClass
    {
        public string Name {get;set;}
        public int[] pIntArray { get; set; }
        public Address Address { get; set; }
        public DemoEnum DemoEnum { get; set; }

        // DeepCopyDemoClass    TestB  ,TestB     DeepCopyDemoClass  ,         
        public TestB TestB {get;set;}

        public override string ToString()
        {
            return "DeepCopyDemoClass";
        }
    }

    [Serializable]
    public class TestB
    {
        public string Property1 { get; set; }

        public DeepCopyDemoClass DeepCopyClass { get; set; }

        public override string ToString()
        {
            return "TestB Class";
        }
    }

    [Serializable]
    public struct Address
    {
        public string City { get; set; }
    }

    public enum DemoEnum
    {
        EnumA = 0,
        EnumB = 1
    }

の で、この の について が えたのは らなかったので、 ってから えてから、 し えました.まず えられるのは、1つの で オブジェクトが された を できるかどうか、よく えてみると、 が まり、 な の は のように されます.
public class DeepCopyHelper
    {
        //                              
        static Dictionary typereflectionCountDic = new Dictionary();

 public static T DeepCopyWithReflection_Second(T obj)
        {
            Type type = obj.GetType();

            //                
            if (obj is string || type.IsValueType) return obj;

            if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);
                for (int i = 0; i  1)
                return obj; //      

            object retval = Activator.CreateInstance(obj.GetType());

            PropertyInfo[] properties = obj.GetType().GetProperties(
                BindingFlags.Public | BindingFlags.NonPublic
                | BindingFlags.Instance | BindingFlags.Static);
            foreach (var property in properties)
            {
                var propertyValue = property.GetValue(obj, null);
                if (propertyValue == null)
                    continue;
                property.SetValue(retval, DeepCopyWithReflection_Second(propertyValue), null);
            }

            return (T)retval;
        }
        private static int Add(Dictionary dict, Type key)
        {
            if (key.Equals(typeof(String)) || key.IsValueType) return 0;
            if (!dict.ContainsKey(key))
            {
                dict.Add(key, 1);
                return dict[key];
            }

            dict[key] += 1;
            return dict[key];
        }
}

に、 のコードがループ の を したかどうかをコードでテストします. なテストコードは の りです.
class Program
    {
        static void Main(string[] args)
        {
            //ShallowCopyDemo();
            //ListShallowCopyDemo();
            DeepCopyDemo();
            DeepCopyDemo2();
        }
        private static void WriteLog(string msg)
        {
            Console.WriteLine(msg);
        }

        public static void DeepCopyDemo()
        {
            DeepCopyDemoClass deepCopyClassA = new DeepCopyDemoClass();
            deepCopyClassA.Name = "DeepCopyClassDemo";
            deepCopyClassA.pIntArray = new int[] { 1 };
            deepCopyClassA.DemoEnum = DemoEnum.EnumA;
            deepCopyClassA.Address = new Address() { City = "Shanghai" };

            deepCopyClassA.TestB = new TestB() { Property1 = "TestProperty", DeepCopyClass = deepCopyClassA };

            //             
            DeepCopyDemoClass deepCopyClassB = DeepCopyHelper.DeepCopyWithBinarySerialize(deepCopyClassA);
            deepCopyClassB.Name = "DeepCopyClassDemoB";
            WriteLog(string.Format("    Name->[A:{0}] [B:{1}]", deepCopyClassA.Name, deepCopyClassB.Name));
            deepCopyClassB.pIntArray[0] = 2;
            WriteLog(string.Format("    intArray->[A:{0}] [B:{1}]", deepCopyClassA.pIntArray[0], deepCopyClassB.pIntArray[0]));
            deepCopyClassB.Address = new Address() { City = "Beijing" };
            WriteLog(string.Format("    Addressstruct->[A: {0}] [B: {1}]", deepCopyClassA.Address.City, deepCopyClassB.Address.City));
            deepCopyClassB.DemoEnum = DemoEnum.EnumB;
            WriteLog(string.Format("    DemoEnum->[A: {0}] [B: {1}]", deepCopyClassA.DemoEnum, deepCopyClassB.DemoEnum));
            deepCopyClassB.TestB.Property1 = "TestPropertyB";
            WriteLog(string.Format("    Property1->[A:{0}] [B:{1}]", deepCopyClassA.TestB.Property1, deepCopyClassB.TestB.Property1));
            WriteLog(string.Format("    TestB.DeepCopyClass.Name->[A:{0}] [B:{1}]", deepCopyClassA.TestB.DeepCopyClass.Name, deepCopyClassB.TestB.DeepCopyClass.Name));
            Console.WriteLine();
        }

        public static void DeepCopyDemo2()
        {
            DeepCopyDemoClass deepCopyClassA = new DeepCopyDemoClass();
            deepCopyClassA.Name = "DeepCopyClassDemo";
            deepCopyClassA.pIntArray = new int[] { 1, 2 };
            deepCopyClassA.DemoEnum = DemoEnum.EnumA;
            deepCopyClassA.Address = new Address() { City = "Shanghai" };

            deepCopyClassA.TestB = new TestB() { Property1 = "TestProperty",  DeepCopyClass = deepCopyClassA };

            //           
            DeepCopyDemoClass deepCopyClassB = DeepCopyHelper.DeepCopyWithReflection_Second(deepCopyClassA);

            //DeepCopyDemoClass deepCopyClassB = DeepCopyHelper.DeepCopyWithReflection(deepCopyClassA);
            deepCopyClassB.Name = "DeepCopyClassDemoB";
            WriteLog(string.Format("    Name->[A:{0}] [B:{1}]", deepCopyClassA.Name, deepCopyClassB.Name));
            deepCopyClassB.pIntArray[0] = 2;
            WriteLog(string.Format("    intArray->[A:{0}] [B:{1}]", deepCopyClassA.pIntArray[0], deepCopyClassB.pIntArray[0]));
            deepCopyClassB.Address = new Address() { City = "Beijing" };
            WriteLog(string.Format("    Addressstruct->[A: {0}] [B: {1}]", deepCopyClassA.Address.City, deepCopyClassB.Address.City));
            deepCopyClassB.DemoEnum = DemoEnum.EnumB;
            WriteLog(string.Format("    DemoEnum->[A: {0}] [B: {1}]", deepCopyClassA.DemoEnum, deepCopyClassB.DemoEnum));
            deepCopyClassB.TestB.Property1 = "TestPropertyB";
            WriteLog(string.Format("    Property1->[A:{0}] [B:{1}]", deepCopyClassA.TestB.Property1, deepCopyClassB.TestB.Property1));
            WriteLog(string.Format("    TestB.DeepCopyClass.Name->[A:{0}] [B:{1}]", deepCopyClassA.TestB.DeepCopyClass.Name, deepCopyClassB.TestB.DeepCopyClass.Name));
            Console.ReadKey();
        }
    }

このときの を に します.
はこのような を て、サイクル の を したと っていましたが、この が に されたので、StackOverflowerのエラーはなくなりました.しかし、よく ると、 シーケンス と が した いコピーの は なり、 のように の い が た である. らかに, シーケンス の には りはなく, されている コードには がある. は えます.なぜ に されたコードが しくないのですか?
DeepCopyWithReflection_を しく Secondのコードは、 のコードの い が っていることに づきました.
int reflectionCount = Add(typereflectionCountDic, obj.GetType());            if (reflectionCount > 1)                return obj; //     

DeepCopyWithReflection_Secondメソッドは、TestBを すると、DeepCopyClassプロパティに すると、DeepCopyWithReflection_が に び されます.Secondメソッドでは、typereflectionCountDicでDeepCopyDemoClassが されていることを した 、 ります.このように するのは いないようですが、この はdeepCopyClassAオブジェクトが されますが、deepCopyClassBオブジェクトが されます.つまり、deepCopyClassBオブジェクトのメモリ は のようになります.
 
deepCopyClassBオブジェクトのメモリ は、 の に されています.
DeepCopyWithReflectionを つけた Secondのエラーの は、 たちは しなければなりません. たちが ってきたのはdeepCopyClassBオブジェクトですが、 したdeepCopyClassBオブジェクトはどのように られますか?ここでは、CreateInstanceメソッドで したdeepCopyClassBオブジェクトを で できますか?アイデアを する の はコードです.そうすれば、 はこの えに ってDeepCopyWithReflection_Secondはまた され、 なコードは のようになります.
public static T DeepCopyWithReflection_Third(T obj)
        {
            Type type = obj.GetType();

            //                
            if (obj is string || type.IsValueType) return obj;

            if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);
                for (int i = 0; i  1 && obj.GetType() == typeof(DeepCopyDemoClass))
                return (T)DeepCopyDemoClasstypeRef; //   deepCopyClassB  

            object retval = Activator.CreateInstance(obj.GetType());

            if(retval.GetType() == typeof(DeepCopyDemoClass))
                DeepCopyDemoClasstypeRef = retval; //         DeepCopyDemoClass  

            PropertyInfo[] properties = obj.GetType().GetProperties(
                BindingFlags.Public | BindingFlags.NonPublic
                | BindingFlags.Instance | BindingFlags.Static);
            foreach (var property in properties)
            {
                var propertyValue = property.GetValue(obj, null);
                if (propertyValue == null)
                    continue;
                property.SetValue(retval, DeepCopyWithReflection_Third(propertyValue), null);
            }

            return (T)retval;
        }

はDeepCopyWithReflectionを いますThirdメソッドでテストすると、 なテストコードは の りです.
class Program
    {
        static void Main(string[] args)
        {
            //ShallowCopyDemo();
            //ListShallowCopyDemo();
            DeepCopyDemo();
            DeepCopyDemo2();
        }
         private static void WriteLog(string msg)
        {
            Console.WriteLine(msg);
        }

        public static void DeepCopyDemo()
        {
            DeepCopyDemoClass deepCopyClassA = new DeepCopyDemoClass();
            deepCopyClassA.Name = "DeepCopyClassDemo";
            deepCopyClassA.pIntArray = new int[] { 1 };
            deepCopyClassA.DemoEnum = DemoEnum.EnumA;
            deepCopyClassA.Address = new Address() { City = "Shanghai" };

            deepCopyClassA.TestB = new TestB() { Property1 = "TestProperty", DeepCopyClass = deepCopyClassA };

            //             
            DeepCopyDemoClass deepCopyClassB = DeepCopyHelper.DeepCopyWithBinarySerialize(deepCopyClassA);
            deepCopyClassB.Name = "DeepCopyClassDemoB";
            WriteLog(string.Format("    Name->[A:{0}] [B:{1}]", deepCopyClassA.Name, deepCopyClassB.Name));
            deepCopyClassB.pIntArray[0] = 2;
            WriteLog(string.Format("    intArray->[A:{0}] [B:{1}]", deepCopyClassA.pIntArray[0], deepCopyClassB.pIntArray[0]));
            deepCopyClassB.Address = new Address() { City = "Beijing" };
            WriteLog(string.Format("    Addressstruct->[A: {0}] [B: {1}]", deepCopyClassA.Address.City, deepCopyClassB.Address.City));
            deepCopyClassB.DemoEnum = DemoEnum.EnumB;
            WriteLog(string.Format("    DemoEnum->[A: {0}] [B: {1}]", deepCopyClassA.DemoEnum, deepCopyClassB.DemoEnum));
            deepCopyClassB.TestB.Property1 = "TestPropertyB";
            WriteLog(string.Format("    Property1->[A:{0}] [B:{1}]", deepCopyClassA.TestB.Property1, deepCopyClassB.TestB.Property1));
            WriteLog(string.Format("    TestB.DeepCopyClass.Name->[A:{0}] [B:{1}]", deepCopyClassA.TestB.DeepCopyClass.Name, deepCopyClassB.TestB.DeepCopyClass.Name));
            Console.WriteLine();
        }

        public static void DeepCopyDemo2()
        {
            DeepCopyDemoClass deepCopyClassA = new DeepCopyDemoClass();
            deepCopyClassA.Name = "DeepCopyClassDemo";
            deepCopyClassA.pIntArray = new int[] { 1, 2 };
            deepCopyClassA.DemoEnum = DemoEnum.EnumA;
            deepCopyClassA.Address = new Address() { City = "Shanghai" };

            deepCopyClassA.TestB = new TestB() { Property1 = "TestProperty",  DeepCopyClass = deepCopyClassA };

            //           
            DeepCopyDemoClass deepCopyClassB = DeepCopyHelper.DeepCopyWithReflection_Third(deepCopyClassA);

            //DeepCopyDemoClass deepCopyClassB = DeepCopyHelper.DeepCopyWithReflection(deepCopyClassA);
            deepCopyClassB.Name = "DeepCopyClassDemoB";
            WriteLog(string.Format("    Name->[A:{0}] [B:{1}]", deepCopyClassA.Name, deepCopyClassB.Name));
            deepCopyClassB.pIntArray[0] = 2;
            WriteLog(string.Format("    intArray->[A:{0}] [B:{1}]", deepCopyClassA.pIntArray[0], deepCopyClassB.pIntArray[0]));
            deepCopyClassB.Address = new Address() { City = "Beijing" };
            WriteLog(string.Format("    Addressstruct->[A: {0}] [B: {1}]", deepCopyClassA.Address.City, deepCopyClassB.Address.City));
            deepCopyClassB.DemoEnum = DemoEnum.EnumB;
            WriteLog(string.Format("    DemoEnum->[A: {0}] [B: {1}]", deepCopyClassA.DemoEnum, deepCopyClassB.DemoEnum));
            deepCopyClassB.TestB.Property1 = "TestPropertyB";
            WriteLog(string.Format("    Property1->[A:{0}] [B:{1}]", deepCopyClassA.TestB.Property1, deepCopyClassB.TestB.Property1));
            WriteLog(string.Format("    TestB.DeepCopyClass.Name->[A:{0}] [B:{1}]", deepCopyClassA.TestB.DeepCopyClass.Name, deepCopyClassB.TestB.DeepCopyClass.Name));
            Console.ReadKey();
        }
}

このときの は、 のようになります.
  
の から,このときの コピーの は に ないことが かった.この は, オブジェクトのループ も に した.
、まとめ
ここまでで、この の は わります.ここでは、1 の で に したことを に し、 による いコピーには にも くの があることがわかりますので、 はシーケンス した で いコピーを うことをお めします.
に のすべてを します×××:DeepCopy.zip