当前位置:网站首页>[C#] 彻底搞明白深拷贝

[C#] 彻底搞明白深拷贝

2022-04-23 17:02:00 code bean

每一种语言都有深拷贝这个概念,其实本质上都是一样的,这次从C#的角度解释。

C# 有值类型和引用类型。值类型,如int之类的不存在浅和深的问题,直接赋值就完了。

引用类型,比如我们自定义的类。通过new的方式创建,new返回的其实是个“指针”,

或者说是引用,他自己在栈上,但是new分配的空间在堆上。 如果直接对引用赋值的话,

相当于是改变“指针”的指向。之前被他管理的堆内存,就被悬空了,最后没人指向它,就会被垃圾回收机制给回收了。

比如,我自定义一个类。

        public class Box
        {
            public double height;   // 高度
            public double width;    // 宽度
            
        }

然后new两个对象,并将b1直接赋值给b2

Box b1 = new Box();
Box b2 = new Box();

b2 = b1;
b2.width = 5; //改变b2 影响b1
Console.WriteLine(b1.width);

此时b2和b1指向了同一块内存,b2之前分配的内存被悬空了,最后被GC回收。

那如果不想这样,我想拷贝一个独立的b2(b2的改变不影响b1),那么唯一的办法就是逐个拷贝。

        public static Box FixDeepCopy(double height, double width)
        {
            Box b = new Box();
            b.height = height;
            b.width = width;
            return b;
        }

这里我们注意到,我们没有改变b的指向,所以b指向的还是最开始自己new出的内存。由于逐一的赋值,所以此时算是深拷贝成功了。

但是这样过于麻烦,不同类,要写不同的深拷贝函数,而且随着类越复杂,方法越繁琐。

那么下面三种方法,帮你解决,仔细观察,其实这三种方法,其实也是将成员变量逐一拷贝的,

只是做了泛化处理。

不多说了,直接上代码了:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;

namespace DeepCopyTest
{
    internal class Program
    {
        [Serializable]
        public class Box
        {
            public double height;   // 高度
            public double width;    // 宽度
            
        }

        public static Box FixDeepCopy(double height, double width)
        {
            Box b = new Box();
            b.height = height;
            b.width = width;
            return b;
        }


        public static T DeepCopyByBin<T>(T obj)
        {
            object retval;
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                //序列化成流
                bf.Serialize(ms, obj);
                //将当前流中的位置设置为指定值。也就是从SeekOrigin.Begin偏移0个位置。
                ms.Seek(0, SeekOrigin.Begin);
                //反序列化成对象
                retval = bf.Deserialize(ms);
                ms.Close();
            }
            return (T)retval;
        }


        /// <summary>
        /// 注意需要给类加个[Serializable]
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static T DeepCopyByReflect<T>(T obj)
        {
            //如果是字符串或值类型则直接返回
            if (obj is string || obj.GetType().IsValueType) return obj;

            object retval = Activator.CreateInstance(obj.GetType());
            FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
            foreach (FieldInfo field in fields)
            {
                try { field.SetValue(retval, DeepCopyByReflect(field.GetValue(obj))); }
                catch { }
            }
            return (T)retval;
        }



        static void Main(string[] args)
        {
            Box b1 = new Box();
            Box b2 = new Box();
            Box b3 = new Box();
            Box b4 = new Box();
            Console.WriteLine(b1.width);

            //直接赋值的方式
            b2 = b1;
            b2.width = 5; //改变b2 影响b1
            Console.WriteLine(b1.width);


            Console.WriteLine("--------------方式1----------------------");
            //深拷贝
            b2 = DeepCopyByBin(b1);
            b2.width = 6; //改变b2 不影响b1
            Console.WriteLine(b1.width);


            Console.WriteLine("---------------方式2---------------------");       
            //深拷贝
            b3 = DeepCopyByReflect(b1);
            b3.width = 7; //改变b5 不影响b1
            Console.WriteLine(b1.width);

            Console.WriteLine("----------------方式3--------------------");
            //深拷贝(需要安装包:Newtonsoft.Json)
            b4 = JsonConvert.DeserializeObject<Box>(JsonConvert.SerializeObject(b1)); //深克隆
            b4.width = 789; //改变b4 不影响b1
            Console.WriteLine(b1.width);


            Console.ReadKey();
        }
    }
}

小结:

1 感觉用 JsonConvert最方便,需要自己安装一个包Newtonsoft.Json(这个包太常用了)

JsonConvert.DeserializeObject<Box>(JsonConvert.SerializeObject(b1));

2 使用 BinaryFormatter的方式,注意需要给类加个可序列化的特性[Serializable],不然运行报错。

它比较直接,直接拷贝整个内存。

3 最后反射这种方式,要注意一下,因为可以看到,它用到了递归的方式,去检索每一个对象。

如果对象过于复杂,可能有点问题。

版权声明
本文为[code bean]所创,转载请带上原文链接,感谢
https://blog.csdn.net/songhuangong123/article/details/124365854