C#——XML序列化
开发环境
VS2022
.net core 6.0
序列化概念
序列化是将内存中的对象或者对象图(一组相互引用的对象)拉平为一个可以保存或进行传输的字节流,或者XML节点。反序列化正好相反,它把数据流重新构造成内存中的一个对象或者对象图。
序列化用途
序列化和反序列化通常用于:
· 通过网络或程序边界传输对象
· 在文件或者数据库中保存对象
此外,它还可以用于深度克隆对象。而数据契约和XML序列化引擎也可以当作通用工具,用于加载和保存已知结构的XML文件。
可序列化的项
-
公共类的公共读/写属性和字段。
-
执行 ICollection 或 IEnumerable 的类 。
-
XmlElement 对象。
-
XmlNode 对象。
-
DataSet 对象。
序列化常用特性
XmlRoot——申明根结点,如: [XmlRoot(ElementName = "Root")]
XmlIgnore——忽悠某个属性或字段,如 :[XmlIgnore] public int Id;
XmlInclude——是否包含某个类,如:[XmlInclude(typeof(Results))]
XmlArray——申明集合
XmlArrayItem——申明集合中元素,如下:
[XmlArray("Targets", Namespace = "TwoPoint")]
[XmlArrayItem("Target", Namespace = "TwoPoint")]
public Point[] Points { get; set; }
XML 序列化注意事项
使用 XmlSerializer 类时,应注意以下事项:
-
序列化数据只包含数据本身和类的结构。
-
只能序列化公共属性和字段。 属性必须具有公共访问器(get 和 set 方法)。
-
类必须具有无参数构造函数才能被 XmlSerializer 序列化。
-
方法不能被序列化。
-
如下所述,如果实现 IEnumerable 或 ICollection 的类满足某些要求,XmlSerializer 则可以处理这些类 。
实现 IEnumerable 的类必须实现采用单个参数的公共 Add 方法 。
实现 ICollection 的类(如 CollectionBase)必须具有采用整型的公共“Item”索引属性(在 C# 中为索引器),而且它必须有一个“integer”类型的公共“Count”属性 。 传递给 Add 方法的参数必须与从“Item”属性返回的类型相同,或者为此类型的基之一 。
常见错误及解决办法
以下为愚初次使用时遇到的一引问题,及解决办法:
不同命名空间下的相同类名下异常
InvalidOperationException: Types 'WpfApp.Xml.Results.Target' and 'WpfApp.Xml.PlanInfo.Target' both use the XML type name, 'Target', from namespace ''. Use XML attributes to specify a unique XML name and/or namespace for the type.
解决办法:如上黄色字体,即为序列化的对象添加Namespace,如下
[XmlType(Namespace = "WpfApp.Xml.Results")]
public class Target
{
/// <summary>
/// 编号
/// </summary>
[XmlAttributeAttribute(AttributeName = "id")]
public double NO { get; set; }
}
注意仅需要添加Namespace即可,不需要添加TypeName。
很多文章提到应该添加TypeName和Namespace,如下的操作方式:
[XmlType(TypeName = "PTarget", Namespace = "WpfApp.Xml.Info")]
public class Target
{
/// <summary>
/// 编号
/// </summary>
[XmlAttributeAttribute(AttributeName = "id")]
public double NO { get; set; }
}
但若添加了TypeName会导致你后一个不同NameSpace下的类改为你Namespace中设置的名称。
非集合属性或字段添加了集合特性
InvalidOperationException: For non-array types, you may use the following attributes: XmlAttribute, XmlText, XmlElement, or XmlAnyElement.
集合属性或字段添加了 XmlElement
集合不能使用它,否则会导致它的子元素全部显示为Targets,而应该如下:
[XmlArray("Targets")]
[XmlArrayItem("Target")]
public Point[] Points { get; set; }
XmlArray指定了集合Points在Xml中的显示名称,
XmlArrayItem指定了集合中元素在Xml中的显示名称。
对象序列化与反序列化常用代码
对象序列化为XML文件
/// <summary>
/// 序列化对象为XML文件
/// </summary>
internal static void XmlSerializer<T>(string fileName, T t) where T : new()
{
try
{
XmlSerializer serializer = new(typeof(SerializationWrapper));
using TextWriter tr = new StreamWriter(fileName, false, Encoding.UTF8);
// 序列化包装类
serializer.Serialize(tr, t);
tr.Close();
tr.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine("序列化失败");
Exception exception = new("序列化失败");
throw exception;
}
}
xml文件反序化为对象
/// <summary>
/// 反序列化XML文件为对象
/// </summary>
/// <param name="fileName">文件名</param
internal static T XmlDeserialize<T>(string fileName) where T : new()
{
using FileStream s = File.OpenRead(fileName);
// 创建包装类的实例并添加对象
XmlSerializer xs = new(typeof(T));
T sw = (T)xs.Deserialize(s);
return sw;
}
上述代码最好还是添加上try……catch……以进行错误处理。
对象序列化为字符串
/// <summary>
/// 将对象序列化为XML字符串
/// </summary>
/// <typeparam name="T">需要序列化的类型</typeparam>
/// <param name="t">序列化的对象实例</param>
/// <returns>序列化对象的字符串</returns>
internal static string Object2XmlStringSerializer<T>(T t) where T : new()
{
try
{
// 创建包装类的实例并添加对象
T wrapper = t;
// 创建XmlSerializer的实例,指定根元素名称
XmlSerializer serializer = new(typeof(T));
// 使用StringWriter来捕获序列化后的XML内容
using (StringWriter stringWriter = new())
{
serializer.Serialize(stringWriter, wrapper);
string xmlContent = stringWriter.ToString();
Debug.WriteLine(xmlContent);
return xmlContent;
}
}
catch (Exception ex)
{
Debug.WriteLine("序列化失败");
Exception exception = new("序列化失败");
throw exception;
}
}
若需要指定编码方式,那么就需要转化为数据流,然后指定相应的编码方式后再读取,如下:
using (MemoryStream memoryStream = new ())
{
using (StreamWriter streamWriter = new (memoryStream, Encoding.UTF8))
{
serializer.Serialize(streamWriter, t);
streamWriter.Flush();
memoryStream.Position = 0;
using (StreamReader streamReader = new (memoryStream, Encoding.UTF8))
{
string xmlContent = streamReader.ReadToEnd();
Debug.WriteLine(xmlContent);
return xmlContent;
}
}
}
其实没有必要指定编码方式,从测试结果来看,反序列化时并不需要指定相应的编码方式,也可以正确进行解码。
xml字符串反序化为对象
/// <summary>
/// 将XML字符串反序列化为对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="xmlString"></param>
/// <returns></returns>
internal static T XmlStringDeserializer<T>(string xmlString) where T : new()
{
// 使用StringReader包装XML字符串
using StringReader stringReader = new(xmlString);
try
{
XmlSerializer serializer = new(typeof(T));
// 反序列化XML字符串
T root = (T)serializer.Deserialize(stringReader);
return root;
}
catch (Exception ex)
{
throw ex;
}
}
参考资料
《C# 核心技术指南(原书第7版)》
XML 序列化详细信息 - .NET | Microsoft Learn