XmlSerializer 序列化总结
一、汇总
序列化反序列化例程 参考微软官网 ,所有相关问题理论上都能在里面找到原因。注意里面说到 XmlSerializer new的时候只有俩种方式才不会发生内存泄漏。其余会出现dll不释放的内存泄漏。
XmlSerializer(Type)
XmlSerializer.XmlSerializer(Type, String)
1.节点
碰到的问题,就是1)类节点要重命名 ,后面找到“XmlTypeAttribute”;2)数组定义对象不想数组名占用节点,找到“XmlElementAttribute”;3)类定义默认节点名称,使用“XmlTypeAttribute”
节点/属性重命名,一般方式是 XmlXXXX(XXXXName=“新的节点名”);
节点参考“Tozhang ”的博客
XmlElement 节点重命名
XmlRoot 根节点重名称
XmlArrayList集合添加根节点
XmlArrayItemList集合中子节点重命名
[Serializable] 将该类标记为可以序列化类
[XmlRoot(“root”)]可以指定重新指定xml根节点的名称,若不加这特性,此类在序列化时候,会默认使用类名作为根节点
[XmlRootAttribute("Slab", IsNullable = false)]
[XmlElement(“code”)]
[XmlIgnore] 此特性是忽略此属性
[XmlAttribute(“attr”)] 此属性会作为特性在 元素中
[XmlTypeAttribute(TypeName = "Segment")] //设定类的节点名称 是“Segment” public class SegmentXXX {
……
}
[XmlRootAttribute("MyCity", Namespace="abc.abc", IsNullable=false)] // 当该类为Xml根节点时,以此为根节点名称。
public class City
[XmlAttribute("AreaName")] // 表现为Xml节点属性。<... AreaName="..."/>
public string Name
[XmlElementAttribute("AreaId", IsNullable = false)] // 表现为Xml节点。<AreaId>...</AreaId>
public string Id
[XmlArrayAttribute("Areas")] // 表现为Xml层次结构,根为Areas,其所属的每个该集合节点元素名为类名。<Areas><Area ... /><Area ... /></Areas>
public Area[] Areas
[XmlElementAttribute("Area", IsNullable = false)] // 表现为水平结构的Xml节点。<Area ... /><Area ... />... 对比上面就是少掉“Areas”节点,少一次缩进。
public Area[] Areas
[XmlIgnoreAttribute] // 忽略该元素的序列化。
2. 改变XML序列化的默认值
参考“yubinfeng ”博客。博客描述很详细,作为第二个示例
遇到问题,要去除开头的xml语句,并去掉根节点的xml名空间声明。
/// <summary> /// 返回去除节点头部的 /// </summary> /// <param name="filename"></param> /// <param name="xtw"></param> /// <param name="xmlSN"></param> private void ReturnXmlWrite(string filename, out XmlWriter xtw, out XmlSerializerNamespaces xmlSN) { //设置序序化XML格式 MemoryStream ms = new MemoryStream(); XmlWriterSettings xws = new XmlWriterSettings(); xws.Indent = true; //获取或设置一个值,该值指示是否缩进元素。 xws.OmitXmlDeclaration = true;//获取或设置一个值,该值指示是否省略XML声明。 xws.Encoding = System.Text.Encoding.UTF8;//设定编码,读取的时候同样编码,可以省略xml带编码行 xtw = XmlTextWriter.Create(filename, xws); //去掉要结点的 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 属性 xmlSN = new XmlSerializerNamespaces(); xmlSN.Add("", ""); }
3.反射错误
3.1 类命和内部成员变量冲突
执行的时候一直报反射错误。查很久,发现是“同名属性错误”
/// <summary> /// 平板周长定义,必须。逆时针相对于平板边界框的左下角给出坐标 /// </summary> [XmlTypeAttribute(TypeName = "Zone")] public class MainZone { [XmlElement(ElementName = "Zone")] public Zone unPickZone; public SlabZone() { segments = new Segment[4]; for (int i = 0; i < 4; i++) segments[i] = new Segment(); unPickZone = new Zone(); } }
上面代码在执行 = new XmlSerializer(Type) 会弹窗报错。
原因是 "MainZone "关联的属性名“Zone” 和 成员对象的类“Zone”冲突。
更改方法:俩个里面挑一个重命名为其它。
3.2 类Xml类型属性(XmlTypeAttribute)和数组节点属性名冲突(XmlElementAttribute)冲突
[XmlTypeAttribute(TypeName = "Path")] public class SegmentPolyLine { /// </summary> [XmlElementAttribute(ElementName = "Segment")] public EdgeDefinition[] segmentPath; …… } [XmlTypeAttribute(TypeName = "Segment")] public class CuttingEdgeDefinition { [XmlAttribute] public double X1 = 0; [XmlAttribute] public double Y1 = 0; …… }
以上在序列化生成对象的时候将报反射错误,进一步是XMl数据类型不能唯一确定。解析:同样的节点数据,可能是对象数组,也可能是单个对象。具体反射哪一类,不能唯一确定
解决方法:去掉“[XmlElementAttribute(ElementName = "Segment")]” 和“ [XmlTypeAttribute(TypeName = "Segment")]” ,任何一者。解除耦合。
4. 显示顺序
变量先输出;属性后输出。
public class ExportInfo { private string code; [XmlAttribute] public string Code { get { return code; } set { code = value; } } private string description; [XmlAttribute] public string Description { get { return description; } set { description = value; } }
[XmlAttribute]
public int Surface;
}
以上定义输出结果是
<ExportInfo Surface="6083920" Code="FD210104092902050" Description="XXXXX" />
"Surface"做如下更改后,按顺序输出。
private int surface = 0; [XmlAttribute] public int Surface { get { return surface; } set { if (value >= 0) surface = value; } }
序列化输出结果
<ExportInfo Code="FD210104092902050" Description="XXXXX" Surface="6083920"/>
5. 注意事项
从yubinfeng 转载过来。
XML序列化一些注意事项
(1)要序列化的类必须有默认的构造的构造函数,才能使用XmlSerializer序列化,需要序列化的类都必须有一个无参的构造函数(通过对基础中类和类的实例学习,我们必须知道类不定义构造函数的情况下,会默认生成一个无参数的构造函数);
补充:如果变量只声明,没有赋值,序列化后是没有对应的节点和属性值。
(2)索引器、私有字段或只读属性(只读集合属性除外)不能被序列化;
(3)不想序列化时:当不想序列化一个属性时,使用[System.Xml.Serialization.XmlIgnore]标记,能用于属性;[NonSerializable]应用于属性无效,能用于类,结构体等;
(4)方法不能被序列化(虽然是废话,但是还是列举出来);
(5)枚举变量可序列化为字符串,无需用[XmlInclude]
(6)导出非基本类型对象,都必须用[XmlInclude]事先声明。该规则递归作用到子元素 。可以参考 spacer_robot
(7)Attribute中的IsNullable参数若等于false,表示若元素为null则不显示该元素。(针对值类型有效)
(8)某些类就是无法XML序列化的(即使使用了[XmlInclude])
比如:
IDictionary(如HashTable);
父类对象赋予子类对象值的情况;
对象间循环引用;
(9)对于无法XML序列化的对象,可考虑:
使用自定义xml序列化(实现IXmlSerializable接口);
实现IDictionary的类,可考虑(1)用其它集合类替代;(2)用类封装之,并提供Add和this函数;
某些类型需要先经过转换,然后才能序列化为 XML。如XML序列化System.Drawing.Color,可先用ToArgb()将其转换为整数;
过于复杂的对象用xml序列化不便的话,可考虑用二进制序列化;
(10)默认构造函数是必须的,因为反序列化本质上使用的是反射,需要默认构造函数来实例化类,如果去掉其中的默认构造函数,则编译没有问题,但运行就会报错。
尽量不要将比较大的属性放在默认构造函数初始化,那会导致在反序列化时对列表初始化两次:默认构造函数中执行一次,反序列化时从XML文档读取再执行一次。
以上十点注意,其中前三点,也就是加黑的这几点,是重点要知道的。
二.引用
引用来源:1.https://www.cnblogs.com/yubinfeng/p/4631838.html
2.https://www.cnblogs.com/zhang1f/p/11666930.html
3.https://www.cnblogs.com/zsh_robot/articles/1323949.html
4.https://www.cnblogs.com/yukaizhao/archive/2011/07/22/xml-serialization.html 博客里面还有其它方式处理xml
5.https://docs.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializer?view=net-5.0 微软官方文档