在C#开发中,将内存中的业务对象转换成XML格式字符串是一项基础操作。很多接口协议或本地缓存只想要纯粹的节点数据,不需要开头的声明信息。如果使用框架自带的XmlSerializer直接序列化,往往会得到包含声明头和默认命名空间的前缀内容,这会给后续解析或拼接带来麻烦。本文围绕如何去除这些多余部分,给出几种可靠的实现方式。

使用XmlSerializer与XmlWriterSettings去除声明
最核心的思路是借助XmlSerializer执行序列化,但把输出导向一个配置了XmlWriterSettings的写入器,并将OmitXmlDeclaration属性设为true。这样写入器在生成内容时就会跳过<?xml version="1.0" encoding="utf-8"?>这一段。与此同时,如果不希望节点上出现xsi和xsd相关的命名空间,还需要在序列化时传入一个空的XmlSerializerNamespaces对象。
下面是一段完整的可运行示例,演示如何把一个简单的订单对象序列化为不带声明和命名空间前缀的字符串:
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
public class Order
{
public int Id { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
}
class Program
{
static void Main()
{
Order order = new Order
{
Id = 1001,
ProductName = "键盘",
Price = 299.9m
};
XmlSerializer serializer = new XmlSerializer(typeof(Order));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Indent = true;
settings.Encoding = new UTF8Encoding(false);
StringBuilder sb = new StringBuilder();
using (XmlWriter writer = XmlWriter.Create(sb, settings))
{
serializer.Serialize(writer, order, ns);
}
string xmlString = sb.ToString();
Console.WriteLine(xmlString);
}
}
上述代码运行后,xmlString的值为<Order><Id>1001</Id><ProductName>键盘</ProductName><Price>299.9</Price></Order>,完全没有声明头和多余命名空间。这种方式的优点是序列化过程仍然由框架完成,属性映射、字段顺序、特殊字符转义都由XmlSerializer内部处理,比手工拼字符串安全得多。
需要注意的是,UTF8Encoding(false)的作用是让写入器不要自己加上UTF-8的BOM标记,否则某些严格解析的接口会因为开头不可见字符而报错。另外如果对象图中包含循环引用,原生XmlSerializer会抛出异常,此时需要改用其他序列化方案或在模型上做扁平化处理。
对比DataContractSerializer与手动拼接方案
除了XmlSerializer,部分老项目会使用DataContractSerializer。它同样可以通过DataContractSerializerSettings和XmlWriterSettings配合来省略声明,但默认行为更偏向契约模型,要求类标记[DataContract]和[DataMember]。如果仅仅为了去掉声明,两者代价相近,但XmlSerializer对普通POCO更友好,不需要额外特性修饰。
另一种极端做法是放弃序列化器,直接在代码里用StringBuilder拼接XML字符串。例如针对上面的Order对象,手动写sb.Append("<Order>")等语句。这种做法在结构极简单且性能敏感的场景下确实少了一层反射开销,但一旦属性值含有<、>、&等字符,就必须自己调用SecurityElement.Escape或类似方法处理,否则生成的片段是非法的XML,下游解析必然失败。
从可维护性角度看,框架序列化器自动处理转义、空值、集合展开等细节,而手动拼接在这些边界情况上极易遗漏。因此除非是一次性脚本,生产代码中推荐坚持使用XmlSerializer加配置屏蔽声明的做法。下表列出三种方式在去声明场景下的差异:
| 方式 | 去声明难度 | 转义安全 | 适用场景 |
|---|---|---|---|
| XmlSerializer+Settings | 低 | 高 | 常规对象转XML |
| DataContractSerializer | 低 | 高 | 已有契约特性项目 |
| StringBuilder拼接 | 无(天然无) | 低 | 极简结构一次性处理 |
处理复杂对象与编码细节的避坑指南
当对象包含嵌套子对象或集合属性时,XmlSerializer依然能正常展开,只要保证子类型在序列化器构造时已知或通过XmlInclude声明。去声明的配置方式不变,依旧是空的命名空间集合加OmitXmlDeclaration=true。但若在Asp.Net Core等环境里直接把结果返回给前端,还要注意响应头的Content-Type应是application/xml,避免浏览器将其当HTML解析。
一个常见误区是认为设置Encoding为ASCII就能去声明,实际上编码只影响字节流,声明头是否写出完全由OmitXmlDeclaration控制。还有人试图在得到带声明的字符串后用Substring截断,这非常脆弱,因为声明长度随编码和独立属性变化,且可能意外切掉合法数据。正确做法始终是在写入阶段抑制声明生成。
如果你的对象需要实现IXmlSerializable自定义读写,那么在WriteXml方法里同样不要手写声明,调用方传入的XmlWriter若已配置省略声明,就会自然输出纯净内容。总之,无论模型简单还是复杂,把握住写入器配置与命名空间清空两个关键点,就能稳定地在C#中将任意可序列化对象转为不带XML声明的字符串。
C#XML序列化XmlSerializer修改时间:2026-08-13 21:39:31