C#系列——记一次业务需求:对象的深拷贝

简介:

 这篇随笔着实在意料之外,主要是源于上周开发BS的一个业务,需要用到对象的深拷贝。说的直白一点,就是将对象内存分配区和引用完全拷贝一份新的。这种需求以前就遇到过,怎么解决的已经记不清了。这次趁着这个机会将对象的深拷贝这个知识点记录下。

  先来说说业务场景,直接上代码:

复制代码
       //0.反射得到工厂属性
            var lstRes = new List<List<DragElementProp>>();
            var oType = typeof(Ewin.CommonLib.DtoModel.DTO_TM_PLANT);
            var lstAttr = ReflectorAttribute(oType);

            //1.给每个工厂对象的属性赋值,构造前台需要的数据结构
            var lstPropModel = oType.GetProperties();
            foreach (var oModel in lstModel)
            {
                var lstResTmp = new List<DragElementProp>();
                foreach (var oAttr in lstAttr)
                {
                    var oPropModel = lstPropModel.FirstOrDefault(x => x.Name == oAttr.Name);
                    if (oPropModel == null)
                    {
                        continue;
                    }
                    oAttr.Value = oPropModel.GetValue(oModel);
lstResTmp.Add(oAttr); }
lstRes.Add(lstResTmp); }
复制代码

需求就是lstAttr变量保存的是一个List<DragElementProp>类型的集合,需要遍历lstModel,需要将每一个的oModel的Name属性的值赋给lstAttr实例的Value属性。然后保存多个lstAttr的集合,形如List<List<DragElementProp>>。通过上面的代码在foreach (var oModel in lstModel)里面每次new一个新的var lstResTmp = new List<DragElementProp>();来保存赋值后lstAttr,明眼人一看就知道这种方式肯定不行,因为C#里面class是引用类型,每次改变的都是唯一的一个lstAttr实例,通过上面代码的方式得到的lstRes肯定会是几个相同的lstAttr,即最后一次赋值的lstAttr。

  怎么办?各种百度、各种博客园。查了多篇博文,发现答案千篇一律,深拷贝对象的三种解决方案:

  • 实现ICloneable接口,自定义拷贝功能
  • 序列化/反序列化类实现
  • 通过反射实现

 

我们逐一看看这几种方式

(1)实现ICloneable接口的方式,贴上园子里面的代码

复制代码
public class Person:ICloneable 
{ 
    public int Age { get; set; } 
    public string Address { get; set; } 
    public Name Name { get; set; } 
    public object Clone() 
    { 
      Person tem = new Person(); 
      tem.Address = this.Address; 
      tem.Age = this.Age; 
      tem.Name = new Name(this.Name.FristName, this.Name.LastName); 
      return tem; 
    } 
} 
复制代码

很显然,这种方式不可取。如果一个类里面有多个其他类成员,那不是每个都要去定义这样一个clone方法。太low。

 

(2)序列化反序列化方式。贴上园子里面的代码

复制代码
[Serializable] 
public class Person : ICloneable 

public
object Clone()   {     using (MemoryStream ms = new MemoryStream(1000))     {       object CloneObject;       BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));       bf.Serialize(ms, this);       ms.Seek(0, SeekOrigin.Begin);       // 反序列化至另一个对象(即创建了一个原对象的深表副本)       CloneObject = bf.Deserialize(ms);       // 关闭流       ms.Close();       return CloneObject;     }   }
}
复制代码

这种方式比上面方式好一点,但是需要对象是可序列化的,即要加上[Serializable]特性标签,博主试过如果一个普通的类调用这个方法会报异常。

博主用Newtonsoft.Json重新写了个:

复制代码
       foreach (var oModel in lstModel)
            {
                var lstResTmp = new List<DragElementProp>();
                foreach (var oAttr in lstAttr)
                {
                    var oPropModel = lstPropModel.FirstOrDefault(x => x.Name == oAttr.Name);
                    if (oPropModel == null)
                    {
                        continue;
                    }
                    oAttr.Value = oPropModel.GetValue(oModel);
                }
                //深拷贝一个集合到另一个集合
                var oJsonValue = Newtonsoft.Json.JsonConvert.SerializeObject(lstAttr);
                lstResTmp.AddRange(Newtonsoft.Json.JsonConvert.DeserializeObject<List<DragElementProp>>(oJsonValue));
                lstRes.Add(lstResTmp);
            }
复制代码

这种方式对对象没什么太特殊的要求。

 

(3)反射的方式,博主自己简单写了一个:

复制代码
     public static T CloneModel<T>(T oModel)
        {
            var oRes = default(T);
            var oType = typeof(T);

            //得到新的对象对象
            oRes = (T)Activator.CreateInstance(oType);

            //给新的对象复制
            var lstPro = oType.GetProperties();
            foreach (var oPro in lstPro)
            {
                //从旧对象里面取值
                var oValue = oPro.GetValue(oModel);

                //复制给新的对象
                oPro.SetValue(oRes, oValue);
            }

            return oRes;
        }
复制代码

这种方式也比较简单,但考虑到反射得性能问题,而且如果是clone集合,需要遍历去反射这样效率就更低。

 

  综上所述:要深拷贝一个对象,其实上述无论哪种方式都是新产生一个对象,然后给新的对象依次赋值来实现。方案一一般不可取,方案二在集合的序列化方便可能效率稍微高点,方案三如果只是简单的拷贝一个对象我觉得也是不错的选择。反正博主更加偏好方案二,用起来简单。

  

  反正找了好久说的都这三种方式,这次先记录下,如果没有更好的方式就用这些方案先解决吧,当然,如果以后知道了更好的方式也可以拿出来和大家分享。也不知道.Net是否预留了某些特殊的通道来处理这种深拷贝。希望知道的大侠多多指教~~






本文转自懒得安分博客园博客,原文链接:http://www.cnblogs.com/landeanfen/p/4678534.html,如需转载请自行联系原作者

目录
相关文章
|
5月前
|
Java C#
C# 面向对象编程解析:优势、类和对象、类成员详解
OOP代表面向对象编程。 过程式编程涉及编写执行数据操作的过程或方法,而面向对象编程涉及创建包含数据和方法的对象。 面向对象编程相对于过程式编程具有几个优势: OOP执行速度更快,更容易执行 OOP为程序提供了清晰的结构 OOP有助于保持C#代码DRY("不要重复自己"),并使代码更易于维护、修改和调试 OOP使得能够创建完全可重用的应用程序,编写更少的代码并减少开发时间 提示:"不要重复自己"(DRY)原则是有关减少代码重复的原则。应该提取出应用程序中常见的代码,并将其放置在单一位置并重复使用,而不是重复编写。
70 0
|
1天前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
101 65
|
1月前
|
C# 数据安全/隐私保护
C# 一分钟浅谈:类与对象的概念理解
【9月更文挑战第2天】本文从零开始详细介绍了C#中的类与对象概念。类作为一种自定义数据类型,定义了对象的属性和方法;对象则是类的实例,拥有独立的状态。通过具体代码示例,如定义 `Person` 类及其实例化过程,帮助读者更好地理解和应用这两个核心概念。此外,还总结了常见的问题及解决方法,为编写高质量的面向对象程序奠定基础。
17 2
|
5月前
|
C#
C#的类和对象的概念学习案例刨析
【5月更文挑战第17天】C#是一种面向对象的语言,以类和对象为核心。类作为对象的模板,定义了属性(如Name, Age)和行为(如Greet)。对象是类的实例,可设置属性值。封装通过访问修饰符隐藏实现细节,如Customer类的私有name字段通过Name属性访问。继承允许新类(如Employee)从现有类(Person)继承并扩展。多态让不同对象(如Circle, Square)共享相同接口(Shape),实现抽象方法Area,提供灵活的代码设计。
64 1
|
5月前
|
C#
C#对象初始化器
C#对象初始化器
|
5月前
|
存储 C#
C#对象和类
C#对象和类
40 0
|
5月前
|
存储 C#
C#基础语法(类和对象)
C#基础语法(类和对象)
37 2
|
5月前
|
XML 存储 JSON
C# | 使用Json序列化对象时忽略只读的属性
将对象序列化成为Json字符串是一个使用频率非常高的功能。Json格式具有很高的可读性,同时相较于XML更节省空间。 在开发过程中经常会遇到需要保存配置的场景,比如将配置信息保存在配置类型的实例中,再将这个对象序列化成为Json字符串并保存。当需要加载配置时,则是读取Json格式的字符串再将其还原成配置对象。在序列化的过程中,默认会将所有公开的属性和字段都序列化进入Json字符串中,这其中也会包含只读的属性或字段,而只读的属性和字段在反序列化的过程中其实是无意义的,也就是说这一部分存储是多余的。 本文将讲解如何在执行Json序列化时,忽略掉那些只读的属性和字段。
157 0
C# | 使用Json序列化对象时忽略只读的属性
|
5月前
|
Java 编译器 C#
【从Java转C#】第三章:对象和类型
【从Java转C#】第三章:对象和类型