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

来源:互联网 发布:苟利国家网络什么意思 编辑:程序博客网 时间:2024/04/29 22:50

背景

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返回值即可,有关具体的实现方式等有时间再补上。

原创粉丝点击