序列化的概念
1.序列化是将内存中的对象或者对象图(一组相互引用的对象)拉平为一个可以保存或传输的字节流,或者XML节点。
2.反序列化与序列化相反
3.序列化一般用于网络或程序边界传输,文件或数据库保存
4.克隆对象
序列化引擎
1.数据契约序列化器
2.二进制序列化器
3.XML序列化器
4.IXmlSerializable接口
格式化器
格式化器会根据特定的媒介或序列化上下文确定最终的格式
显示序列化和隐式序列化
1.显示序列化:通过请求来序列化或反序列化对象。需要同时选择序列化引擎和格式化器
2.隐式序列化:有Framework工作。发生在递归序列化子对象时和依赖序列化的功能
数据契约的序列化
1.决定选用DataContractSerializer还是NetDataContractSerializer
2.选用[DataContract]和[DataMember]修饰要序列化的类型和成员
3.实例化序列化器后调用WriteObject和ReadObject
DataContractSerializer与NetDataContractSerializer
1.DataContractSerializer需要指定类型
2.NetDataContractSerializer不需要指定类型
使用序列化器
1.DataContractSerializer
Student s = new Student{Age = 1,Name = "zhang"};
//序列化器
var rs = new DataContractSerializer(typeof(Student));
using (Stream st = File.Create("student.xml"))
{
rs.WriteObject(st, s);
}
Student s2;
using (Stream st = File.OpenRead("student.xml"))
{
s2 = (Student)rs.ReadObject(st);
}
Console.WriteLine(s2.Name);
2.NetDataContractSerializer
找不到该类
3.使用XmlWrite,使输出格式换行
XmlWriterSettings setting = new XmlWriterSettings() { Indent = true };
using (XmlWriter st = XmlWriter.Create("student.xml", setting))
{
rs.WriteObject(st, s);
}
4.改变类型名称
[DataContract(Name = "Person")]
class Student
{
[DataMember(Name = "Stu.Age")]
public int Age { get; set; }
[DataMember]
public string Name { get; set; }
}
5.指定命名空间
[DataContract(Namespace ="http://www.feiniao.top")]
class Student
{
[DataMember(Name = "Stu.Age")]
public int Age { get; set; }
[DataMember]
public string Name { get; set; }
}
6.没有[DataContract]的类无法被识别,会报异常。没有[DataMember]的成员无法被序列化
7.指定二进制格式化器
Student s = new Student{Age = 1,Name = "zhang" };
//序列化器
var rs = new DataContractSerializer(typeof(Student));
var ms = new MemoryStream();
using (XmlDictionaryWriter st = XmlDictionaryWriter.CreateBinaryWriter(ms))
{
rs.WriteObject(st, s);
}
var sm2 = new MemoryStream(ms.ToArray());
Student s2;
using (XmlDictionaryReader st = XmlDictionaryReader.CreateBinaryReader(sm2,XmlDictionaryReaderQuotas.Max))
{
s2 = (Student)rs.ReadObject(st);
}
Console.WriteLine(s2.Name);
序列化子类
[DataContract]
public class Student
{
[DataMember]
public int Age { get; set; }
[DataMember]
public string Name { get; set; }
}
[DataContract]
public class Stu1 : Student { }
[DataContract]
public class Stu2 : Student { }
子类必须在序列化器中注册,否则无法识别
static Student DeepClone(Student p)
{
var ds = new DataContractSerializer(typeof(Student),new Type[] {typeof(Stu1),typeof(Stu2) });
var ms = new MemoryStream();
ds.WriteObject(ms, p);
ms.Position = 0;
return (Student)ds.ReadObject(ms);
}
调用
Student s = new Student { Age = 1, Name = "zhang" };
Stu1 stu1 = new Stu1 { Age = 1, Name = "zhang" };
Stu2 stu2 = new Stu2 { Age = 1, Name = "zhang" };
Student s2 = DeepClone(s);
Stu1 stu11 = (Stu1)DeepClone(stu1);
Stu2 stu22 = (Stu2)DeepClone(stu2);
序列化后的根任然是Student,但是会增加一个属性,指定类型为各自的类型(type=Stu1)
对象引用
1.类中引用其他的类也会同时序列化,但是其他的类也需要制定[DataContract]标签,类中需要被序列化的属性也要使用[DataMember]标签
2.直接用子类来代替父类序列化都是不可行的,必须制定Type。方法如下面2种
var ds = new DataContractSerializer(typeof(Student),new Type[] {typeof(Stu1) });
[DataContract,KnownType(typeof(Stu1))]
3.保留对象引用。即2个相同对象是否会应用同一份数据。可以选择引用同一份数据,也可以选择不引用同一份数据。各有利弊
不引用同一份数据(默认情况)
Student s = new Student
{
Age = 1,
Name = "zhang",
School1 = new School
{
Country = "CN",
Location = "SH"
},
};
s.School2 = s.School1;
var ds = new DataContractSerializer(typeof(Student));
using (Stream st = File.Create("student.xml"))
{
ds.WriteObject(st, s);
}
<?xml version="1.0" encoding="utf-8"?>
<Student xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/Net">
<Age>1</Age>
<Name>zhang</Name>
<School1>
<Country>CN</Country>
<Location>SH</Location>
</School1>
<School2>
<Country>CN</Country>
<Location>SH</Location>
</School2>
</Student>
引用对象
Student s = new Student
{
Age = 1,
Name = "zhang",
School1 = new School
{
Country = "CN",
Location = "SH"
},
};
s.School2 = s.School1;
var set = new DataContractSerializerSettings()
{
PreserveObjectReferences = true,
};
var ds = new DataContractSerializer(typeof(Student),set);
XmlWriterSettings setting = new XmlWriterSettings() { Indent = true };
using (XmlWriter st = XmlWriter.Create("student.xml", setting))
{
ds.WriteObject(st, s);
}
<?xml version="1.0" encoding="utf-8"?>
<Student xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="1"
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"
xmlns="http://schemas.datacontract.org/2004/07/Net">
<Age>1</Age>
<Name z:Id="2">zhang</Name>
<School1 z:Id="3">
<Country z:Id="4">CN</Country>
<Location z:Id="5">SH</Location>
</School1>
<School2 z:Ref="3" i:nil="true" />
</Student>
版本容错性
1.版本容错性指可以添加或删除数据成员,而不破坏向前或向后兼容性
2.默认规则反序列化:跳过没有[DataMember]修饰的数据。如果没有任何[DataMember],反序列化不会受到影响。
3.反序列化时要求xml文件必须含有某属性
[DataMember(IsRequired =true)]
如果文件没有该属性,则会抛出异常
4.处理文件中多余的对象。一般情况下,使用2中的规则。
可以使用IExtensibleDataObject来保存未识别的对象,以便以后能用。也就是读取原来的值后,每个该赋值的对象都赋值,而没有匹配的对象则存储在一个黑盒中。当写入的时候,序列化器会自动的将黑盒中的信息一起写入文件,结果和读取的文件一模一样。这样也不会破坏原来文件的结构,可以做到版本兼容
成员顺序
序列化后的顺序:
1.从基类到子类型
2.从低Order到高Order(不指定则最大)
[DataMember(Order = 0)]
3.字母顺序
null和空值
1.处理null和值为空的数据方式:显示写入null或空值(默认方式)。序列化输出是忽略这些数据成员
2.显示的null
<Name i:nil="true" />
3.默认值
<Age>0</Age>
4.取消默认设置,将null或默认值得属性不显示,直接忽略该属性
[DataMember(EmitDefaultValue =false)]
数据契约与集合
1.序列化时,会将所有集合都序列化成一种格式
2.反序列时,如果指定的是一个具体的集合类型,则可以正常解析(List)。如果指定的是一个接口类型,则默认为数组。若想指定集合类型则处理方法如下
[DataMember(Name = "Schools")]
List<School> _school;
public IList<School> Schools { get { return _school; } }
子类集合元素
子类集合处理和前面的处理基本上是一样的
自定义集合元素与元素名称
[DataContract]只能定义单个类的名称。
[CollectionDataContract]定义一个集合
[CollectionDataContract(ItemName = "AAAAAAAAAA")]
public class Schools:Collection<School>
{
}
[DataContract]
public class School
{
[DataMember]
public string Location { get; set; }
[DataMember]
public string Country { get; set; }
}
扩展契约数据
序列化与反序列化钩子
1.如果要在序列化之前执行一段自定义方法,用[OnSerializing]
2.如果要在序列化之后执行一段自定义方法,用[OnSerialized]
3.如果要在反序列化之前执行一段自定义方法,用[OnDeserializing]
4.如果要在反序列化之后执行一段自定义方法,用[OnDeserialized]
5.反序列化器绕过字段初始化和构造器,可以在[OnSerializing]初始化某些数据
[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
}
与[Serializable]的互操作
1.数据契约序列化器也可以序列化那些以二进制序列化引擎中的特性和接口所标记的类型
[DataContract,KnownType(typeof(School))]
public class Student
{
[DataMember(EmitDefaultValue =false)]
public string Name { get; set; }
[DataMember(IsRequired =true)]
public int Age { get; set; }
[DataMember]
public List<School> Schools { get; set; }
}
[Serializable]
public class School
{
public string Location { get; set; }
public string Country { get; set; }
}
二进制序列化器
1.实现二进制序列化只要使用[Serializable]特性或者ISerializable接口
2.[Serializable]特性无法被继承
3.二进制格式化器,BinaryFormatter,SoapFormatter(找不到)
School school = new School
{
Country = "as",
Location = "bd"
};
IFormatter formatter = new BinaryFormatter();
using (var s = File.Create("student.bin"))
formatter.Serialize(s, school);
using (var s = File.OpenRead("student.bin"))
School rs = (School)formatter.Deserialize(s);
二进制序列化特性
[NonSerialized]
序列化时忽略该字段(属性不能用这个特性修饰)
[OptionalField]特性和版本
1.标记一个新的字段为一个版本,防止反序列化时由于类修改了而导致解析错误
2.避免重命名或删除自字段
3.避免追溯地添加[NonSerialized]特性
4.永远不要改变字段的类型
使用ISerializable接口进行二进制序列化
1.GetObjectData会在序列化时触发,将序列化的所有字段放入字典中
2.如果类不是封闭类,最好使用虚函数
[Serializable]
public class School :ISerializable
{
//解析的时候要用,必须
protected School(SerializationInfo info, StreamingContext context)
{
Location = info.GetString("Location");
Country = info.GetString("Country");
}
public School(){}
public string Location;
public string Country;
public string Sui;
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Location", Location);
info.AddValue("Country", Country);
}
}
3.通过版本号来获取冲突值
4.继承可序列化类,不这么做会使子类出现问题(忽略某值得序列化)
[Serializable]
public class Student : School
{
public string Sui;
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("Sui", Sui);
}
protected Student(SerializationInfo info, StreamingContext context):base(info, context)
{
Sui = info.GetString("Sui");
}
public Student(){}
}
XML序列化
基于特性的序列化入门
1.使用XmlSerializer
School school = new School
{
Country = "as",
Location = "fd"
};
XmlSerializer xmlSerializer = new XmlSerializer(typeof(School));
using (Stream s = File.Create("school.xml"))
{
xmlSerializer.Serialize(s,school);
}
School school2;
using (Stream s = File.OpenRead("school.xml"))
{
school2 = (School)xmlSerializer.Deserialize(s);
Console.WriteLine(school2.Country);
}
2.XmlSerializer可以序列化没有任何特性标记的类,默认序列化所有公有字段和属性。
3.可以用[XmlIgnore]来忽略不需要的属性
4.XmlSerializer反序列化时会调用无参的构造函数,没有会报异常
5.将字段变成属性
public class School
{
public string Location;
public string Country;
[XmlAttribute("newName")]public int Age;
}
6.默认顺序为类中的顺序,也可以指定Order来表示顺序
public class School
{
[XmlElement(Order =2)]public string Location;
[XmlElement(Order = 1)] public string Country;
[XmlAttribute]public int Age;
}
越小顺序越前
子类和子对象
1.使父类理解子类(在父类中注册子类)
[XmlInclude(typeof(School1))]
[XmlInclude(typeof(School2))]
public class School
{
[XmlElement(Order =2)]public string Location;
[XmlElement(Order = 1)] public string Country;
[XmlAttribute]public int Age;
}
public class School1 : School{}
public class School2 : School{}
2.在序列化时指定子类
XmlSerializer xmlSerializer =
new XmlSerializer(typeof(School),new Type[] {typeof(School1),typeof(School2) });
3.会自动序列化引用的其他对象,但是和其他的序列化不同的是无法保证引用相等性(当内部的属性引用内部其他的属性时,会序列化2次,而不会引用)
序列化集合
1.[XmlArray("top")]可以定义集合的根节点
2.[XmlArrayItem("son")]可以定义每一个集合自己的根节点
3.可以取消集合的根节点(不定义),使用[XmlElement]
IXmlSerializable接口
1.IXmlSerializable会绕过XmlSerializer的序列化规则
2.IXmlSerializable.ReadXml应当读取最外层内容,然后读取内容,最后才是最外层结束元素
3.IXmlSerializable.WriteXml应当只写入内容
public class School :IXmlSerializable
{
[XmlElement(Order =2)]public string Location;
[XmlElement(Order = 1)] public string Country;
[XmlAttribute]public int Age;
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement();
Location = reader.ReadElementContentAsString("lllooo", "");
Country = reader.ReadElementContentAsString("CCCCyyyy", "");
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteElementString("lllooo", Location);
writer.WriteElementString("CCCCyyyy", Country);
}
}