在C#开发里,把XML节点上的属性读取到字典中是常见的数据转换需求,比如读取配置节、解析第三方返回的报文头。借助.NET自带的XML类库,我们可以非常直观地完成这件事,而不必手写复杂的字符串截取。下面先通过一张示意图了解整体处理流程。

使用XmlDocument读取属性到字典
XmlDocument是.NET早期就提供的DOM解析类,适合需要随机访问节点、修改结构的场景。要从某个节点读取属性到字典,核心是先获取到目标XmlNode,然后遍历它的Attributes集合。每一个XmlAttribute都有Name和Value两个属性,正好对应字典的键和值。
需要注意,如果节点没有属性,Attributes可能为null,直接遍历会抛出空引用异常。因此代码中要先做判空。另外,XML属性值总是字符串,如果业务需要其他类型,要在存入字典后自行转换。下面是一段完整示例,演示如何从根节点下的第一个子元素提取属性:
using System;
using System.Collections.Generic;
using System.Xml;
class Program
{
static void Main()
{
string xml = "<root><item id='1001' name='测试' enabled='true'/></root>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
XmlNode node = doc.DocumentElement.FirstChild;
Dictionary<string, string> dict = new Dictionary<string, string>();
if (node.Attributes != null)
{
foreach (XmlAttribute attr in node.Attributes)
{
dict[attr.Name] = attr.Value;
}
}
foreach (var kv in dict)
{
Console.WriteLine(kv.Key + " = " + kv.Value);
}
}
}
上面的写法采用字典索引赋值,遇到同名属性会直接覆盖,不会抛异常,逻辑简单。如果你希望遇到重复键时报错,可以把赋值改为dict.Add(attr.Name, attr.Value),并在外层捕获ArgumentException。对于大多数配置读取,覆盖策略更省心,但接口报文解析时建议严格校验。
XmlDocument的另一个特点是区分大小写,属性名在XML里是什么样,字典键就是什么样。如果后续要用不区分大小写的方式查找,可以在创建字典时传入StringComparer.OrdinalIgnoreCase。这在跨平台对接时很有用,因为有些系统生成的属性名大小写不统一。
使用XDocument与LINQ简化转换
从.NET 3.5开始,XDocument提供了更函数式的XML操作方式,代码更短,也更容易配合LINQ做筛选。我们可以先用XElement的Attributes方法拿到属性序列,再用ToDictionary一键转换。这种方式特别适合一次性提取,不需要修改原XML的场景。
使用ToDictionary时,要提供键选择器和值选择器。由于XAttribute的Value可能为null(虽然规范上属性应有值,但某些解析器可能给出空),建议用空合并运算符兜底。下面的例子展示如何从指定路径的元素读取属性字典:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
class Program
{
static void Main()
{
string xml = "<root><item id='2002' title='示例' lock='no'/></root>";
XDocument xdoc = XDocument.Parse(xml);
XElement elem = xdoc.Root.Element("item");
Dictionary<string, string> dict = new Dictionary<string, string>();
if (elem != null)
{
dict = elem.Attributes()
.ToDictionary(a => a.Name.LocalName, a => (string)a.Value ?? string.Empty);
}
foreach (var kv in dict)
{
Console.WriteLine(kv.Key + " : " + kv.Value);
}
}
}
在LINQ写法里,a.Name.LocalName能去掉命名空间前缀,只保留纯属性名,避免字典键里混入xmlns之类的内容。如果XML带有命名空间,直接用a.Name会包含完整名称,可能导致键过长或重复。这一点在处理SOAP报文时要格外小心。
相比XmlDocument,XDocument的空安全更好,但ToDictionary遇到重复键会直接抛InvalidOperationException。如果数据源不可信,可以先GroupBy再自己决定取第一个还是合并,这样比事后捕获异常更优雅。整体而言,新项目推荐用XDocument,老代码维护可以继续用XmlDocument。
异常处理与编码注意事项
读取XML属性到字典时,最容易踩的坑是格式错误导致解析失败,以及属性包含特殊字符。XmlDocument和XDocument在加载非法XML时都会抛出异常,因此生产代码必须把LoadXml或Parse包在try-catch里,至少捕获XmlException,给用户明确提示而不是崩溃。
另一个细节是编码声明。如果XML带<?xml version='1.0' encoding='utf-8'?>,用File.ReadAllText读入再Parse通常没问题;但如果用字节流,要确认StreamReader的编码和声明一致,否则中文属性值会乱码。字典里存了乱码字符串,后期排查非常麻烦。
using System;
using System.Collections.Generic;
using System.Xml;
class SafeReader
{
public static Dictionary<string, string> TryRead(string xml)
{
try
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
var node = doc.DocumentElement.FirstChild;
var dict = new Dictionary<string, string>();
if (node != null && node.Attributes != null)
{
foreach (XmlAttribute a in node.Attributes)
{
dict[a.Name] = a.Value;
}
}
return dict;
}
catch (XmlException ex)
{
Console.WriteLine("XML解析错误: " + ex.Message);
return new Dictionary<string, string>();
}
}
}
上面的安全读取方法返回空字典而不是抛异常,调用方可以根据字典数量判断成功与否。在批量处理很多XML片段时,这种容错能避免一个坏数据中断整个任务。同时,如果属性值可能含换行或制表符,存入字典前可考虑用Trim清理,保持数据干净。
最后提醒,字典不是万能容器。如果属性之间有顺序要求,或者某些属性是数字、日期类型,用Dictionary<string,string>会丢失信息。此时可以定义强类型类,用反射或手动映射把属性填进去。但仅做键值缓存或日志输出时,字典依然是最快最直观的选择。