背景
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方法。
浅拷贝(影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用.
深拷贝(深度克隆):不紧复制对象的基本类,同时也复制原对象中的对象.就是说完全是新对象产生的.
浅 拷贝和深拷贝之间的区别:浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。如果改变目标对象 中引用型字段的值他将反映在原是对象中,也就是说原始对象中对应的字段也会发生变化。深拷贝与浅拷贝不同的是对于引用的处理,深拷贝将会在新对象中创建一 个新的和原是对象中对应字段相同(内容相同)的字段,也就是说这个引用和原是对象的引用是不同的,我们在改变新对象中的这个字段的时候是不会影响到原始对 象中对应字段的内容。所以对于原型模式也有不同的两种处理方法:对象的浅拷贝和深拷贝。"
深拷贝的实现方法
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);
}
}
}
}
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方法。下面是一个代码片段:
/// <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返回值即可,有关具体的实现方式等有时间再补上。