C#--深拷贝和浅拷贝

浅拷贝与深拷贝:

浅拷贝: 仅仅把对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份实体。此时,其中一个的成员对象的改变都会影响到另一个的成员对象。

深拷贝:指的是拷贝一个对象时,不仅仅把对象的引用进行拷贝,还把该对象引用的值也一起拷贝。这样进行深拷贝后的副本对象就和源对象互相独立,其中任何一个的成员对象改动都不会对另外一个成员对象造成影响。

C#实现浅拷贝:调用MemberwiseClone方法,创建一个新的对象,然后复制当前对象的非静态字段的新对象创建一个浅表副本。

C#深拷贝的有三种实现:反射、序列化和表达式树。

一、当要拷贝的类没有互相引用时

反射实现:

// 利用反射实现深拷贝
        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 < array.Length; i++)
                {
                    copied.SetValue(DeepCopyWithReflection(array.GetValue(i)), i);
                }
                return (T)Convert.ChangeType(copied, obj.GetType());
            }

            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(propertyValue), null);
            }

            return (T)retval;
        }

序列化实现:分为xml序列化和二进制序列化,json(没有C#内部实现,需要引用外部的包)等

// 利用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;
        }

表达式树实现:

表达式可以是一个参数(如参数x),一个常数(如常数5),一个加运算(如x+5)等等,可以把几个小的表达式组装在一起成为大的表达式,例如:(x+5)-(++y)。对于这样一个表达式可以用一棵树来表示,如下:

C#--深拷贝和浅拷贝_第1张图片

这就是表达式树,表达式树本身也是一个表达式(大的表达式)。一个表达式也是一棵表达式树,可以说它是一棵小的表达式树。可以把表达式树和表达式认为是一个东西,C#中都用Expression类表示。

表达式树的创建
一、Lambda表达式方法
表达式可以通过Lambda表示创建Expression类型,如下:

Expression> fun = (x, y) => x < y

这种方法创建出的表达式根节点类型为ExpressionType.Lambda,Type类型为返回值类型typeof(bool)。

二、组装法
另一种创建表示的方式是通过Expression类的静态函数“组装”表达式如下:

 ParameterExpression p1 = Expression.Parameter(typeof(int),"x");

 ParameterExpression p2 = Expression.Parameter(typeof(int),"y");

  BinaryExpression expr =Expression.GreaterThan(p1, p2);

我们先创建了两个参数表达式x, y然后用GreaterThan组装在一起,最终的表达式为“x>y”,expr的节点类型为LessThan,Type类型为typeof(bool)

Expression类中有我们所需要所有“组装”表达式的用的静态函数,我们不能直接用new的方法去创建表达式节点,表达式所有的节点需要用Expression类中的静态函数创建。

// 表达式树实现
    public class DeepCopyExp
    {
        private static readonly Func Cache = GetExp();

        private static Func GetExp()
        {
            ParameterExpression expression = Expression.Parameter(typeof(TIn), "p");
            List member = new List();

            foreach (var item in typeof(TOut).GetProperties())
            {
                if (!item.CanWrite)
                    continue;

                MemberExpression property = Expression.Property(expression, typeof(TIn).GetProperty(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                member.Add(memberBinding);
            }

            MemberInitExpression memberInitExpression =
                Expression.MemberInit(Expression.New(typeof(TOut)), member.ToArray());
            Expression> lambda =
                Expression.Lambda>(memberInitExpression, new[] { expression });
            return lambda.Compile();
        }

        public static TOut Copy(TIn tIn)
        {
            return Cache(tIn);
        }
    }

小结:

调用一百万次的时间排序:表达式树最快,反射次之,序列化最慢。

二、当要拷贝的类有互相引用时

经过测试,只有经过修改的反射实现和二进制序列化,可以保证对象引用的正确性。当要拷贝的类有互相引用时,xml序列化会直接报错。表达式树,处理互相引用的类也会有问题,可能表达式树的代码也需要相应修改才可以。

反射实现:用一个字典来存放每个对象的反射次数来避免反射代码的循环递归

public class DeepCopyHelper
    {
        // 用一个字典来存放每个对象的反射次数来避免反射代码的循环递归
        static Dictionary typereflectionCountDic = new Dictionary();
        static object DeepCopyDemoClasstypeRef = null;

        // 利用反射实现深拷贝
        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 < array.Length; i++)
                {
                    copied.SetValue(DeepCopyWithReflection(array.GetValue(i)), i);
                }
                return (T)Convert.ChangeType(copied, obj.GetType());
            }

            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(propertyValue), null);
            }

            return (T)retval;
        }

        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 < array.Length; i++)
                {
                    copied.SetValue(DeepCopyWithReflection(array.GetValue(i)), i);
                }

                return (T)Convert.ChangeType(copied, obj.GetType());
            }

            int reflectionCount = Add(typereflectionCountDic, obj.GetType());
            if (reflectionCount > 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_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];
        }
}

Main方法:调用代码以及测试的类

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            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.DeepCopyWithBinarySerialize(deepCopyClassA);
            DeepCopyDemoClass deepCopyClassB = DeepCopyHelper.DeepCopyWithReflection_Second(deepCopyClassA);
            //DeepCopyDemoClass deepCopyClassB = DeepCopyExp.Copy(deepCopyClassA);
            //DeepCopyDemoClass deepCopyClassB = DeepCopyHelper.DeepCopyWithXmlSerializer(deepCopyClassA);

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

    [Serializable]
    public class DeepCopyDemoClass
    {
        public int id;
        public string Name;
        public int[] pIntArray { get; set; }
        public Address Address { get; set; }
        public DemoEnum DemoEnum { get; set; }

        // DeepCopyDemoClass中引用了TestB对象,TestB类又引用了DeepCopyDemoClass对象,从而造成了相互引用
        public TestB TestB { get; set; }
    }

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

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

    [Serializable]
    public enum DemoEnum
    {
        EnumA = 0,
        EnumB = 1
    }
}

浅拷贝:

using System;

namespace ConsoleApp2
{
    class Class28
    {
        public static void Main(string[] args)
        {
            DeepCopyClass deepCopyClassA = new DeepCopyClass();
            deepCopyClassA.Id = 10;
            deepCopyClassA.DemoEnum = DemoEnum.EnumA;
            deepCopyClassA.address = new Address() { City = "Shanghai" };
            deepCopyClassA.Name = "DeepCopyClassDemo1";
            deepCopyClassA.IntArray = new int[] { 1, 2 };
            deepCopyClassA.TestB = new TestB() { Property1 = 111};

            // 浅拷贝
            DeepCopyClass deepCopyClassB = deepCopyClassA.Clone() as DeepCopyClass;
            deepCopyClassB.Id = 1;
            deepCopyClassB.DemoEnum = DemoEnum.EnumB;
            // 对于引用类型,对象和副本对象引用同一个内存地址,
            // 当在对象或者副本对象修改引用成员,引用类型的成员都会发生变化。
            deepCopyClassB.IntArray[0] = 3;
            deepCopyClassB.TestB.Property1 = 222;

            Console.Write("A==B ? ");
            Console.WriteLine(deepCopyClassA == deepCopyClassB);

            //输出A和B对象
            Console.WriteLine("----------------A--------------");
            deepCopyClassA.Display();
            Console.WriteLine("----------------B--------------");
            deepCopyClassB.Display();

            Console.ReadLine();
        }
    }

    public class DeepCopyClass : ICloneable
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int[] IntArray { get; set; }
        public Address address { get; set; }
        public DemoEnum DemoEnum { get; set; }
        public TestB TestB { get; set; }

        // 实现ICloneable接口的Clone方法
        public object Clone()
        {
            // 调用Object的MemberwiseClone(),创建当前object的浅表副本
            return MemberwiseClone() as DeepCopyClass;
        }

        public void Display()
        {
            Console.WriteLine("id = " + Id);
            Console.WriteLine("Name = " + Name);
            for (int i = 0; i < IntArray.Length; i++)
            {
                Console.WriteLine(string.Format("IntArray[{0}] = {1}", i, IntArray[i]));
            }
            Console.WriteLine("Address.City = " + address.City);
            Console.WriteLine("DemoEnum = " + DemoEnum);
            Console.WriteLine("TestB.Property1 = " + TestB.Property1);
        }
    }

    public class TestB
    {
        public int Property1 { get; set; }
    }

    // 值类型
    public struct Address
    {
        public string City { get; set; }
    }

    // 值类型
    public enum DemoEnum
    {
        EnumA = 0,
        EnumB = 1
    }
}

MSDN关于深拷贝和浅拷贝的介绍:传送门

你可能感兴趣的:(C#,数据结构与算法,浅拷贝,深拷贝)