C#中的shallow copy 与deep copy(一)

C# 专栏收录该内容
6 篇文章 0 订阅

背景

1. 一年前我用vb.net做一个饭工厂饭卡系统,在编码的过程中发现一个问题。

我有一个卡信息类Card:

 public  Class Card
        {
            string CardID;
            string  CardNum;
        }

在修改卡信息时,我的做法是这样的:

-->读取选定卡的所有信息(假设为(Card)A)

-->载入到修改界面中

-->用户提交时如果通过输入检查,则新建一个卡对象{ new Card B=A },因为有很多信息不能修改,所以我用A 来初始化B

-->将界面的数据刷新到B中

-->A=B?保存B:不保存。

 

但是调试的时候惊恐的发现,无论怎么改都是相等的……

在单步中我发现B一修改,A也跟着修改了,当时没当回事自己在类中回了一个copy方法:

public  Class Card
        {
            string CardID;
            string  CardNum;
            pulic Card copy()
           {
              Card tempCard =new Card();
               tempCard .CardIdD=this.CardIdD;
               tempCard .CardNum=this.CardNum;
               return tempCard;
            }
        }


其中比较对象相等时,我也写了一个方法,就是逐一比较属性,不相等就返回false.

 

2.这周调研报表在mvc4下的实现,本来找到一个很好的例子:MVC下实现水晶报表,以为搞定了,结果拿到多项目的解决方案中,跨项目取数据又出现问题,其原因是     数据是在Service层(Service是一个单独的项目)取到,以

   List<TSource>ToList<TSource>(thisIEnumerable<TSource>source)方式返回数据。

    我在View层(也是一个单独的项目)得到的List<TSource>放入用<TSource>支撑的报表中时,运行时发现数据放不进去,查阅很多资料我认为是这个方法是运行时提取数据的(延时加载),报表得到的是一个查询语句,但是View层没有能力访问数据库,所以得不到数据。

    针对以上情况,我决定先把List<TSource>中数据取出来放在一个临时List中再传给水晶报表。

    我完全可以用以前的方法来做,但是此次实体中有很多字段,要是每个查询结果的实体类中都用这样的clone函数,那简直是扯蛋……

    所以此次我花了一天时间来研究C#中实现类值拷贝的方式,优雅地复制副本……

 

 术语解释

这里引用网友Timothy的一段文字:(原文链接如下:http://www.cnblogs.com/haiyang1985/archive/2009/01/13/1375017.html)

 

"C#中有两种类型变量,一种 是值类型变量,一种是引用类型变量。对于前者,copy是属于全盘复制;而对于后者,一般的copy只是浅copy,相当于只传递一个引用指针一样。因此 对于后者进行真正copy的时候,也是最费事的,具体的说,必须为其实现ICloneable接口中提供的Clone方法。

浅拷贝(影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用.
深拷贝(深度克隆):不紧复制对象的基本类,同时也复制原对象中的对象.就是说完全是新对象产生的.

浅 拷贝和深拷贝之间的区别:浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。如果改变目标对象 中引用型字段的值他将反映在原是对象中,也就是说原始对象中对应的字段也会发生变化。深拷贝与浅拷贝不同的是对于引用的处理,深拷贝将会在新对象中创建一 个新的和原是对象中对应字段相同(内容相同)的字段,也就是说这个引用和原是对象的引用是不同的,我们在改变新对象中的这个字段的时候是不会影响到原始对 象中对应字段的内容。所以对于原型模式也有不同的两种处理方法:对象的浅拷贝和深拷贝。"

 

深拷贝的实现方法

            实现深拷贝有很多方法,下面介绍一种比较优雅的方法,可能有什么深层次的缺陷,但是在一般的应用上不会出现问题(作为程序员,不应当说这样不精确的话,日后我有更深层次的理解,记住更新此内容)。
            以下的类属于Extensions命名空间,
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;

namespace Extensions
{
    public static class CloneObject
    {
        /// <summary>
        /// Deep clone a ClassObject (by magma)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="a"></param>
        /// <returns></returns>
        public static T DeepClone<T>(this T a)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, a);
                stream.Position = 0;
                return (T)formatter.Deserialize(stream);
            }
        }
    }
}

任何需要应用此深度拷贝的模型类(实体类)都被指定为可序列化(c#中直接在类定义上加上 [ Serializable] 注解即可),下面是一个实体类:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
    [Serializable]
    public class UserInfoLog : Entity
    {
        public UserInfoLog() {
            Addate = DateTime.Now;
            Uddate = DateTime.Now;
        }
        [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
        public string Id { set; get; }
        [MaxLength(100)]
        [Required]
        public string Url { get; set; }

        public string ChainInfoId { get; set; }

        public virtual ChainInfo ChainInfo { get; set; }

        public string RecordId { get; set; }


        public string SysUserId { get; set; }

        public virtual UserInfo UserInfo { get; set; }
        public string EnterpriseId { get; set; }
        [MaxLength(100)]
        [Required]
        public string Ip { get; set; }
        [StringLength(50)]
        public string Remark { set; get; }

        public DateTime Addate { set; get; }
        [StringLength(20)]
        public string Aduser { set; get; }

        public DateTime Uddate { set; get; }
        [StringLength(20)]
        public string Uduser { set; get; }


    }
}


在需要实现此方法的类上添加Extensions引用,那么在所有obeject及其子类中都会有CloneObject方法。下面是一个代码片段:
(这是在MVC4 controller中调用的,正确的方式应当是在BLL层,因为此方法是将数据处理后递给报表,别人都用不到这个方法,所以我临时写在Controller里面)
        /// <summary>
        /// 返回报表请求的数据
        /// </summary>
        [HttpPost]
        public void TransDateToReport()
        {
            //报表路径
            this.HttpContext.Session["ReportPath"] = System.Web.HttpContext.Current.Server.MapPath("~/") + "Rpts//CrUserList.rpt";
            //报表数据源猎取
            List<UserInfo> UserList = roleInfoService.findAllUserInfo().ToList();
            List<UserInfo> userTemp=new List<UserInfo>();
            foreach (UserInfo element in UserList)
            {
                userTemp.Add(element.DeepClone<UserInfo>());
            }
            this.HttpContext.Session["ReportSource"] = userTemp;
        }


一种优雅实现

最近看一本C#高级编程的书,里面有一种方法用来比较两个对象中非静态对象的值是否相等,而非比较引用,实现的思路很不错:它重写了类的ToString方法,以key/value 形式返回待比较的字段,然后直接比较两个对象的toString返回值即可,有关具体的实现方式等有时间再补上。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

vicanary

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值