C#中深拷贝和浅拷贝的介绍与用法
作者:.NET开发菜鸟 发布时间:2022-11-04 07:42:46
一、什么是深拷贝和浅拷贝
对于所有面向对象的语言,复制永远是一个容易引发讨论的题目,C#中也不例外。此类问题在面试中极其容易被问到,我们应该在了解浅拷贝和深拷贝基本概念的基础上,从设计的角度进一步考虑如何支持对象的拷贝。
在System.Object类中,有一个受保护的方法object.MemberwiseClone(),这个方法实现了对象的复制。事实上,它所实现的就是我们所称的浅拷贝。
深拷贝:指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。这样进行深拷贝后的拷贝对象就和源对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。比如一个黄狗叫大黄,使用克隆术克隆另外一个黄狗叫小黄,这样大黄和小黄就相对独立了,他们不互相影响。在.NET中int,double以及结构体和枚举等。
int a=12;
int c=a;//进行了深拷贝
c=232 //不影响
浅拷贝:指的是拷贝一个对象时,仅仅拷贝对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份实体。此时,其中一个对象的改变都会影响到另一个对象。就像一个人改名了一样,他还是这个人,只不过名字变了而已。
public class YDog
{
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
YDog sourceP = new YDog() { Name = "大黄" };
YDog copyP = sourceP; // 浅拷贝
copyP.Name = "小黄"; // 拷贝对象改变Name值
// 结果都是"小黄",因为实现的是浅拷贝,一个对象的改变都会影响到另一个对象
Console.WriteLine("YDog.Name: [SourceP: {0}] [CopyP:{1}]", sourceP.Name, copyP.Name);
Console.Read();
}
}
所谓的浅拷贝,是指拷贝一个对象的时候,拷贝原始对象中所有的非静态值类型成员和所有的引用类型成员的引用。换言之,新的对象和原始对象将共享所有引用类型成员的实际对象。而相对的,深拷贝是指不仅复制所有的非静态值类型成员,而且也复制所有引用类型成员的实际对象。深拷贝和浅拷贝的概念是递归的,也就是说当引用类型成员中包含另外一个引用类型成员时,拷贝的时候将对其内部成员实行同样的复制策略。
浅拷贝示意图如下所示:
深拷贝示意图如下图所示:
类型基类System.Object已经为所有类型都实现了浅拷贝,类型所要做的就是公开一个复制的接口,而通常的,这个接口会借由实现ICloneable接口来实现。ICLoneable只包含一个Clone方法。该方法既可以被实现为浅拷贝也可以被实现为深拷贝,具体如何取舍需要根据具体类型的需求来决定。下面的代码提供了一个深拷贝的简单示例:
using System;
namespace DeepCopy
{
class Program
{
static void Main(string[] args)
{
// 定义原始对象
DpCopy dc = new DpCopy();
dc._i = 10;
dc._a = new A();
// 定义深拷贝对象
DpCopy deepClone = (DpCopy)dc.Clone();
// 定义浅拷贝对象
DpCopy shadowclone = (DpCopy)dc.MemberwiseClone();
// 深拷贝的复制对象将拥有自己的引用类型成员对象
// 所以这里的赋值不会影响原始对象
deepClone._a._s = "我是深拷贝的A";
Console.WriteLine(dc);
Console.WriteLine(deepClone);
Console.WriteLine("\r\n");
// 浅拷贝的复制对象共享原始对象的引用类型成员对象
// 所以这里的赋值将影响原始对象
shadowclone._a._s = "我是浅拷贝的A";
Console.WriteLine(dc);
Console.WriteLine(shadowclone);
Console.ReadKey();
}
}
public class DpCopy : ICloneable
{
public int _i = 0;
public A _a = new A();
public object Clone()
{
// 实现深拷贝
DpCopy newDc = new DpCopy();
// 重新实例化一个引用类型变量
newDc._a = new A();
// 给新引用类型变量的成员值
newDc._a._s = _a._s;
newDc._i = _i;
return newDc;
}
// 实现浅拷贝
public new object MemberwiseClone()
{
return base.MemberwiseClone();
}
/// <summary>
/// 重写类的ToString()方法
/// </summary>
/// <returns></returns>
public override string ToString()
{
return "I的值为:" + _i.ToString() + ",A为:" + _a._s;
}
}
/// <summary>
/// 包含一个引用成员的类型
/// </summary>
public class A
{
public string _s = "我是原始A";
}
}
在上面的代码中,类型DpCopy通过ICLoneable接口的Clone方法提供了深拷贝,并且通过提供一个MemberwiseClone的公共方法提供了浅拷贝。DpCopy类型具有一个值类型成员和一个引用类型成员,引用类型成员在浅拷贝和深拷贝时将展现不同的特性,浅拷贝的原始对象和目标对象公用了一个引用类型成员对象,这在程序的执行结果中可以清楚地看到:
有的参考资料上说C#中的深拷贝通过ICloneable接口来实现。这句话并不正确。事实上任何名字的方法都可以用来实现深拷贝,并且没有任何语法来规定深拷贝只能通过Clone方法来实现。Clone这个名字只是一种习惯的称呼,而实现ICloneable只能带来一般接口的通用便利性,而并没有任何关于拷贝的特殊性。
一般可被继承的类型应该避免实现ICloneable接口,因为这样做将强制所有的子类型都需要实现ICloneable接口,否则将使类型的深拷贝不能覆盖子类的新成员。
实现深拷贝
1、新建一个对象,一个一个的重新赋值,麻烦一点
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServiceTest
{
public class Program
{
static void Main(string[] args)
{
YDog Dog = new YDog() { Name = "大黄" };
YDog NewDog = new YDog();
NewDog.Name = Dog.Name;
Console.WriteLine($"Dog.Name:{Dog.Name},NewDog.Name:{NewDog.Name}");
Console.Read();
}
}
}
输出结果
2、利用反射实现深拷贝
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace ServiceTest
{
public class Program
{
static void Main(string[] args)
{
YDog Dog = new YDog() { Name = "大黄" };
YDog NewDog = (YDog)DeepCopy(Dog);
NewDog.Name = Dog.Name;
Console.WriteLine($"Dog.Name:{Dog.Name},NewDog.Name:{NewDog.Name}");
Console.Read();
}
/* 利用反射实现深拷贝*/
public static object DeepCopy(object _object)
{
Type T = _object.GetType();
object o = Activator.CreateInstance(T);
PropertyInfo[] PI = T.GetProperties();
for (int i = 0; i < PI.Length; i++)
{
PropertyInfo P = PI[i];
P.SetValue(o, P.GetValue(_object));
}
return o;
}
}
}
输出结果
3、利用序列化和反序列化来实现,如下代码
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;
using System.Xml.Serialization;
namespace ServiceTest
{
public class Program
{
static void Main(string[] args)
{
YDog Dog = new YDog() { Name = "大黄" };
//YDog NewDog = (YDog)DeepCopy(Dog);
//NewDog.Name = Dog.Name;
// 序列化实现
YDog NewDog = (YDog)DeepCopy<YDog>(Dog);
Console.WriteLine($"Dog.Name:{Dog.Name},NewDog.Name:{NewDog.Name}");
Console.Read();
}
/* 利用反射实现深拷贝*/
public static object DeepCopy(object _object)
{
Type T = _object.GetType();
object o = Activator.CreateInstance(T);
PropertyInfo[] PI = T.GetProperties();
for (int i = 0; i < PI.Length; i++)
{
PropertyInfo P = PI[i];
P.SetValue(o, P.GetValue(_object));
}
return o;
}
// 利用XML序列化和反序列化实现
public static T DeepCopyWithXmlSerializer<T>(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>(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;
}
public static T DeepCopy<T>(T obj)
{
// 序列化
string json= JsonConvert.SerializeObject(obj);
// 反序列化
return JsonConvert.DeserializeObject<T>(json);
}
}
}
二、总结
浅拷贝是指复制类型中的所有值类型成员,而只赋值引用类型成员的引用,并且使目标对象共享原对象的引用类型成员对象。深拷贝是指同时复制值类型成员和引用类型成员的对象。浅拷贝和深拷贝的概念都是递归的。System.Object中的MemberwiseClone已经实现了浅拷贝,但它是一个受保护的方法。无论深拷贝还是浅拷贝,都可以通过实现ICloneable接口的Clone方法来实现,可被继承的类型需要谨慎地实现ICloneable接口,因为这将导致所有的子类型都必须实现ICloneable接口。
来源:https://www.cnblogs.com/dotnet261010/p/12329220.html
猜你喜欢
- 一、MySql实现分页查询的SQL语句 1、分页需求:客户端通过传递pageNo(页码),counter(每页显示的条数)两个参数去分页查询
- 本文实例为大家分享了android Matrix图片随意放大缩小和拖动的具体代码,供大家参考,具体内容如下step1:新建一个项目DragA
- 题目:求1+2!+3!+...+20!的和程序分析:此程序只是把累加变成了累乘。程序设计:public class Ex21 {  
- Java 中的内部类这是一个 Java 内部类的简单实现:public class OutterJava { pr
- 确保这个修改是正确的(否则将会出现乱码)创建i18n文件夹(就是国际化的意思),然后在此文件加下创login.properties logi
- 一、JAVA简要概述先说一下java之父,詹姆斯·高斯林这是一个爱喝咖啡而又强大的男人。再来看一下JAVA有多火在TIOBE排行榜上JAVA
- 任何Java代码都可以抛出异常,如:自己编写的代码、来自Java开发环境包中代码,或者Java运行时系统。无论是谁,都可以通过Java的th
- 前言Kotlin一个强大之处就在于它的扩展函数,巧妙的运用这些扩展函数可以让你写出的代码更加优雅,阅读起来更加流畅,下面总结了在开发中经常用
- 前言大家都知道MySQL数据库很好用,但数据量到了千万以上了,想增加字段是非常痛苦的,这个在MongoDB里就不存在,字段想怎么加就怎么加,
- Java 7的这个新特性改变了警告的对象。构建这些类型毕竟有破坏类型安全的风险,这总得有人知道。但 API 的用户对此是无能为力的,不管do
- 在实际的项目开发过中,当我们修改了某个java类文件时,需要手动重新编译、然后重新启动程序的,整个过程比较麻烦,特别是项目启动慢的时候,更是
- 为什么需要全局异常处理在传统 Spring Boot 应用中, 我们 @ControllerAdvice 来处理全局的异常,进行统一包装返回
- 前言人类建造迷宫已有5000年的历史。在世界的不同文化发展时期,这些奇特的建筑物始终吸引人们沿着弯弯曲曲、困难重重的小路吃力地行走,寻找真相
- java 8的新特性之一就是lambda表达式,parallelStream()都说性能会比较高,现一探究竟。话不多说,上代码: @Test
- String 对象是不可改变的。每次使用 System.String 类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对
- 最近看到一个考试系统,有个功能是用来监视进程的。一旦发现如Communicator.exe这样的违禁软件就立即杀死进程并上报给服务器。我稍
- Author:jeffreyDate:2019-04-08一、开发环境:1、mysql - 5.72、navicat(mysql客户端管理工
- 在有些情况下死锁是可以避免的。本文将展示三种用于避免死锁的技术:1.加锁顺序2.加锁时限3.死锁检测加锁顺序当多个线程需要相同的一些锁,但是
- 概述Spring boot 中的 @Conditional 注解是一个不太常用到的注解,但确实非常的有用,我们知道 Spring Boot
- 最近项目中的活动面板要做来回滚动卡牌预览效果,感觉自己来写的话,也能写,但是可能会比较耗时,看到Github上有开源的项目,于是就借用了,G