一、浅克隆和深克隆(浅复制和深复制)
浅克隆和深克隆最典型的应用是数据集对象DataSet的Clone和Copy方法。Clone()方法用来复制DataSet的结构,但是不复制DataSet的数据,实现了浅复制。Copy()方法不但复制结构,也复制数据,实现了深复制。另外:我们熟知的Object对象有一个MemberwiseClone()方法,它实现的就是浅表复制。该方法不是虚拟的,所以不能重写它的实现代码。
1、浅克隆:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
Code is cheap.看代码:
(1)值类型的浅克隆
using System;
namespace ShallowColone
{
/*浅克隆 只有值类型的情况*/
class SourceClass : ICloneable //继承自ICloneable,创建作为当前实例副本的新对象。
{
public SourceClass(string name,int id)
{
this.name = name;
this.id = id;
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private int id;
public int Id
{
get { return id; }
set { id = value; }
}
public object Clone()
{
return this.MemberwiseClone();
}
}
class Test
{
static void Main()
{
SourceClass test = new SourceClass("source class",1);
SourceClass test2 = (SourceClass)test.Clone();
Console.WriteLine("test name:" + test.Name);
Console.WriteLine("test2 name:" + test2.Name);
Console.WriteLine("test id:" + test.Id);
Console.WriteLine("test2 id:" + test2.Id);
if (test.Name == test2.Name && test.Id == test2.Id)
{
Console.WriteLine("See,the value after the clone method is the same!");
}
Console.WriteLine(object.ReferenceEquals(test, test2)); //对象的一个副本,内存地址不是指向同一个引用
test2.Name = "copyied class";
Console.WriteLine("test name:" + test.Id);
Console.WriteLine("test2 name:" + test2.Id);
Console.ReadLine();
}
}
}
ps:注意string类型是比较特殊的值类型
(2)引用类型的浅克隆
using System;
namespace ShallowColone
{
class User
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
/*浅克隆 值类型和引用类型的情况*/
class SourceClass : ICloneable //继承自ICloneable,创建作为当前实例副本的新对象。
{
public SourceClass(string name,int id)
{
this.name = name;
this.id = id;
testUser = new User();//初始化一个引用对象
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private int id;
public int Id
{
get { return id; }
set { id = value; }
}
private User testUser;
public void SetUser(string userName)
{
testUser.Name = userName;
}
public object Clone()
{
return this.MemberwiseClone();
}
public void Display()
{
Console.WriteLine("Hi,i am " + testUser.Name);
}
}
class Test
{
static void Main()
{
SourceClass test = new SourceClass("source class", 1);
test.SetUser("Jeff Wong");
SourceClass test2 = (SourceClass)test.Clone();
test2.SetUser("jeff");
SourceClass test3 = (SourceClass)test.Clone();
test3.SetUser("jeff2");
//下面说明三个实例化对象都是指向同一个引用(最后的那个人jeff2)
test.Display();
test2.Display();
test3.Display();
Console.WriteLine("See,the reference after the clone method is the same!");
Console.ReadLine();
}
}
}
2、深克隆:对于值类型,被复制对象的所有变量都含有与原来的对象相同的值,把要复制的对象的引用的对象都复制一遍。
也就是说,深克隆的值类型和浅克隆一样,但是对于引用类型,深克隆把引用对象的变量指向复制过的新对象,而不是原有的引用的对象。下面改进“引用类型的浅复制”中的示例来说明深克隆的概念:
using System;
namespace ShallowColone
{
class User : ICloneable //用户也继承自ICloneable接口
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public object Clone()
{
return (User)this.MemberwiseClone();
}
}
/*浅克隆 值类型和引用类型的情况*/
class SourceClass : ICloneable //继承自ICloneable,创建作为当前实例副本的新对象。
{
public SourceClass(string name,int id)
{
this.name = name;
this.id = id;
testUser =new User();//***初始化一个引用对象,clone方法***
}
/// <summary>
/// 提供clone方法调用的私有构造函数,以便克隆用户信息
/// </summary>
/// <param name="testUser"></param>
private SourceClass(User testUser)
{
this.testUser = (User)testUser.Clone();
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private int id;
public int Id
{
get { return id; }
set { id = value; }
}
private User testUser;
public void SetUser(string userName)
{
testUser.Name = userName;
}
/// <summary>
/// 深克隆 改进后的克隆方法
/// </summary>
/// <returns></returns>
public object Clone()
{
SourceClass retClass = new SourceClass(this.testUser);//调用私有构造函数,让用户克隆完成
retClass.id = this.id;
retClass.name = this.name;
return retClass;
}
public void Display()
{
Console.WriteLine("Hi,i am " + testUser.Name);
}
}
class Test
{
static void Main()
{
SourceClass test = new SourceClass("source class", 1);
test.SetUser("Jeff Wong");
SourceClass test2 = (SourceClass)test.Clone();
test2.SetUser("jeff");
SourceClass test3 = (SourceClass)test.Clone();
test3.SetUser("jeff2");
//经过深克隆,达到我们设置不同用户的初衷
test.Display();
test2.Display();
test3.Display();
Console.WriteLine("See,the reference after the clone method is the NOT same!");
Console.ReadLine();
}
}
}
二、序列化和反序列化
前言: 当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为对象。 把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。
这里还要注意对象的序列化主要有两种用途:1) 在网络上传送对象的字节序列。2) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
.net为我们主要提供了三种序列化方式:
它们的主要区别请参考下图:
最后来三段代码,简单实现常用的序列化和反序列化。
1、使用BinaryFormatter
using System;
using System.Data;
using System.Configuration;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace BinarySerialize
{
//要使一个类可序列化,最简单的方法是使用Serializable 属性对它进行标记
[Serializable]
public class User //要序列化的用户类
{
public int tid = 252;
public string name = "jeff wong";
[NonSerialized]
public string sex = "male"; // 性别被标识为不可序列化
}
public class SerializeUtil
{
private static string strFile = "c:\\User.data";
public static void Serialize(User testUser)
{
using (FileStream fs = new FileStream(strFile, FileMode.Create))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, testUser);
}
}
public static User DeSerialize()
{
User testUser = null;
using (FileStream fs = new FileStream(strFile, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
testUser = (User)formatter.Deserialize(fs);
}
return testUser;
}
}
public class Program
{
static void Main()
{
SerializeUtil.Serialize(new User());//序列化
User tmpUser = SerializeUtil.DeSerialize();//反序列化
Console.WriteLine(tmpUser.tid + "-" + tmpUser.name);
Console.WriteLine(tmpUser.sex); //sex被标识为不可序列化,所以总为null
Console.ReadLine();
}
}
}
2、使用SoapFormatter
先添加System.Runtime.Serialization.Formatters.Soap引用,然后和BinaryFormatter类似,我们只需要做一下简单修改即可:
a.将using语句中的.Formatter.Binary改为.Formatter.Soap;
b.将所有的BinaryFormatter替换为SoapFormatter.
c.确保报存文件的扩展名为.xml
经过上面简单改动,即可实现SoapFormatter的串行化,这时候产生的文件就是一个xml格式的文件。
using System;
using System.Data;
using System.Configuration;
using System.IO;
using System.Runtime.Serialization.Formatters.Soap;
namespace SoapSerialize
{
//要使一个类可序列化,最简单的方法是使用Serializable 属性对它进行标记
[Serializable]
public class User //要序列化的用户类
{
public int tid = 252;
public string name = "jeff wong";
[NonSerialized]
public string sex = "male"; // 性别被标识为不可序列化
}
public class SerializeUtil
{
private static string strFile = "c:\\User.xml"; //存为xml
public static void Serialize(User testUser)
{
using (FileStream fs = new FileStream(strFile, FileMode.Create))
{
SoapFormatter formatter = new SoapFormatter();
formatter.Serialize(fs, testUser);
}
}
public static User DeSerialize()
{
User testUser = null;
using (FileStream fs = new FileStream(strFile, FileMode.Open))
{
SoapFormatter formatter = new SoapFormatter();
testUser = (User)formatter.Deserialize(fs);
}
return testUser;
}
}
public class Program
{
static void Main()
{
SerializeUtil.Serialize(new User());//序列化
User tmpUser = SerializeUtil.DeSerialize();//反序列化
Console.WriteLine(tmpUser.tid + "-" + tmpUser.name);
Console.WriteLine(tmpUser.sex); //sex被标识为不可序列化,所以总为null
Console.ReadLine();
}
}
}
(3)、使用XmlSerializer
想使用XmlSeralizer进行串行化我们需要做一下修改:
a.添加System.Xml.Serialization命名空间.
b.Serializable和NoSerialized属性将被忽略,而是使用XmlIgnore属性,它的行为与NoSerialized类似.
c.XmlSeralizer要求类有个默认的构造器.
using System;
using System.Data;
using System.Configuration;
using System.IO;
using System.Xml.Serialization;
namespace XmllSerialize
{
[Serializable]
public class User //要序列化的用户类
{
public int tid = 252;
public string name;
[XmlIgnore]
public string sex = "male";
public User(string name)
{
this.name = name;
}
/// <summary>
/// 必须注意:XmlSeralizer要求类有个默认的构造器
/// </summary>
public User()
{
}
}
public class SerializeUtil
{
private static string strFile = "c:\\User.xml"; //存为xml
public static void Serialize(User testUser)
{
XmlSerializer xs = new XmlSerializer(typeof(User));
using (Stream stream = new FileStream(strFile, FileMode.Create, FileAccess.Write, FileShare.Read))
{
xs.Serialize(stream, testUser);
}
}
public static User DeSerialize()
{
User testUser = null;
using (FileStream fs = new FileStream(strFile, FileMode.Open, FileAccess.Read, FileShare.Read))
{
XmlSerializer xs = new XmlSerializer(typeof(User));
testUser = (User)xs.Deserialize(fs);
}
return testUser;
}
}
public class Program
{
static void Main()
{
SerializeUtil.Serialize(new User("jeff wong"));//序列化
User tmpUser = SerializeUtil.DeSerialize();//反序列化
Console.WriteLine(tmpUser.tid + "-" + tmpUser.name);
Console.WriteLine(tmpUser.sex);
Console.ReadLine();
}
}
}
关于常用的XML序列化,可以通过如下通用泛型方法结合扩展方法轻松进行序列化和反序列化操作,代码如下:
using System; using System.IO; using System.Xml; using System.Xml.Serialization; public static class XMLSerializeExtension { public static string Serialize<T>(this T value) { var result = string.Empty; if (value == null) { return result; } try { var serializer = new XmlSerializer(typeof(T)); var stringWriter = new StringWriter(); using (var writer = XmlWriter.Create(stringWriter)) { serializer.Serialize(writer, value); result = stringWriter.ToString(); } } catch (Exception ex) { throw new Exception("An error occurred while XML serialize", ex); } return result; } public static T Deserialize<T>(this string value) { T retObj = default(T); if (string.IsNullOrWhiteSpace(value) == true) { return retObj; } try { using (var rdr = new StringReader(value)) { var serializer = new XmlSerializer(typeof(T)); retObj = (T)serializer.Deserialize(rdr); } } catch (Exception ex) { throw new Exception("An error occurred while XML Deserialize", ex); } return retObj; } }
必须注意,XmlSerializer要慎用, 容易引起动态加载dll引发内存泄漏,这里是内存泄漏的例子。