在C#开发里,把业务对象转换成XML是常见需求,尤其是对接旧系统或调用WebService时。XmlSerializer是官方提供的序列化工具,但很多人发现它默认把对象的公共属性写成一层层的子元素。如果对方接口要求把数据放在节点的属性上,就需要用XmlAttribute特性来控制输出结构。

为什么默认会序列化成元素
XmlSerializer在设计上倾向于用元素来表达对象图,因为它能自然地表达嵌套关系。当你定义一个普通类,里面有几个自动属性,不做任何特性修饰,序列化后每个属性都会成为根节点下的子元素。这种方式可读性好,但会让XML体积变大,而且有些外部系统明确只认属性。
例如下面这段代码,没有任何特性,输出的就是纯元素结构:
using System;
using System.Xml.Serialization;
using System.IO;
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main()
{
User u = new User { Name = "张三", Age = 28 };
XmlSerializer ser = new XmlSerializer(typeof(User));
using (StringWriter sw = new StringWriter())
{
ser.Serialize(sw, u);
Console.WriteLine(sw.ToString());
}
}
}
上面代码运行后得到的XML类似<User><Name>张三</Name><Age>28</Age></User>,所有数据都在子元素里。如果接口要求写成<User Name="张三" Age="28" />,就必须改写法。
使用XmlAttribute特性的基本方法
要把某个字段变成XML属性,只需在属性上方加XmlAttribute特性。加上之后,Serializer就会把它当作当前节点的属性来写,而不是子节点。根类本身也可以用XmlRoot来指定根节点名字,避免和类名耦合。
修改后的类定义如下,注意引用了System.Xml.Serialization命名空间:
using System;
using System.Xml.Serialization;
using System.IO;
[XmlRoot("User")]
public class User
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("age")]
public int Age { get; set; }
}
class Program
{
static void Main()
{
User u = new User { Name = "张三", Age = 28 };
XmlSerializer ser = new XmlSerializer(typeof(User));
using (StringWriter sw = new StringWriter())
{
ser.Serialize(sw, u);
Console.WriteLine(sw.ToString());
}
}
}
这段程序输出的XML会变成<User name="张三" age="28" />,完全符合属性化要求。可以看出,加特性成本极低,却直接改变了序列化形态。
常见限制与避坑点
第一个坑是集合属性不能直接标XmlAttribute。因为XML属性只能装单个简单值,不能放数组或列表。如果你有一个List<string>,只能让它序列化为元素,或者自己先拼成逗号分隔的字符串再标特性。第二个坑是同一个类里属性和元素不能重名,否则反序列化会报异常。
另外,如果父类子类都有同名属性且都标了XmlAttribute,需要用XmlAttributeOverrides在构造Serializer时显式覆盖,否则运行时可能抛InvalidOperationException。下面示例展示如何用覆盖方式处理继承场景:
using System;
using System.Xml.Serialization;
using System.IO;
public class Base
{
public string Id { get; set; }
}
public class Sub : Base
{
[XmlAttribute("id")]
public new string Id { get; set; }
[XmlAttribute("val")]
public int Val { get; set; }
}
class Program
{
static void Main()
{
XmlAttributeOverrides ov = new XmlAttributeOverrides();
XmlAttributes attrs = new XmlAttributes();
attrs.XmlAttribute = new XmlAttributeAttribute("id");
ov.Add(typeof(Base), "Id", attrs);
Sub s = new Sub { Id = "A1", Val = 10 };
XmlSerializer ser = new XmlSerializer(typeof(Sub), ov);
using (StringWriter sw = new StringWriter())
{
ser.Serialize(sw, s);
Console.WriteLine(sw.ToString());
}
}
}
通过覆盖,我们确保基类里的Id也以属性形式出现,避免生成多余元素。实际项目中遇到继承结构时,这一招很实用。
反序列化的对称性
用XmlAttribute序列化出来的XML,在反序列化时不需要额外配置,只要类定义保持一致,XmlSerializer会自动把属性值读回对象。这也说明特性是双向生效的,不必写两套逻辑。
简单演示读取过程:
using System;
using System.Xml.Serialization;
using System.IO;
[XmlRoot("User")]
public class User
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("age")]
public int Age { get; set; }
}
class Program
{
static void Main()
{
string xml = "<User name="李四" age="33" />";
XmlSerializer ser = new XmlSerializer(typeof(User));
using (StringReader sr = new StringReader(xml))
{
User u = (User)ser.Deserialize(sr);
Console.WriteLine(u.Name + " " + u.Age);
}
}
}
只要字符串是合法属性化XML,上面代码就能正确还原对象。由此可见,掌握XmlAttribute特性不仅能控制输出,也能保证输入输出协议统一。
小结
把C#对象序列化为XML属性而不是元素,核心就是给目标属性加XmlAttribute特性,并用XmlRoot规划根节点。遇到集合、继承等复杂情况,再配合字符串预处理或XmlAttributeOverrides即可。这样生成的XML更紧凑,也更易于和严格定义属性的外部系统互通。
C#XmlSerializerXmlAttribute修改时间:2026-08-08 03:06:26