一、首先对比Xml反序列化
性能对比
1.1 性能测试结果如下
Deserialize是反序列化结果
GetResult是基于反射的对象构建方法,比反序列化快33%
GetResult2是定制对象构建组件,避免了反射,比GetResult快一点
Custom继承重写EntityParser,避免字典查找,比GetResult2更快一点
Method Mean Error StdDev Ratio Gen0 Gen1 Allocated Alloc Ratio
Deserialize 1,355.5 ns 4.44 ns 4.75 ns 1.00 0.7240 0.0300 12.2 KB 1.00
GetResult 912.7 ns 5.07 ns 5.84 ns 0.67 0.6750 0.0250 11.38 KB 0.93
GetResult2 798.3 ns 0.40 ns 0.42 ns 0.59 0.6500 - 10.95 KB 0.90
Custom 779.4 ns 6.46 ns 7.43 ns 0.57 0.6500 - 10.95 KB 0.90
1.2 相关代码如下
private static readonly XmlSerializer _serializer = new(typeof(User));
private static readonly EntityParser _parser = HandXml.Default.Entity()
.WithItem(nameof(User.Id))
.WithItem(nameof(User.Name))
.WithItem(nameof(User.Age));
private static readonly EntityParser _parser2 = HandXml.Default.Entity(UserBuilder.Creater)
.WithItem(nameof(User.Id))
.WithItem(nameof(User.Name))
.WithItem(nameof(User.Age));
private static readonly UserParser _customParser = new(HandXml.Default);[Benchmark(Baseline = true)]
public User? Deserialize()
{
using var stringReader = new StringReader(text);
return (User?)_serializer.Deserialize(stringReader);
}
[Benchmark]
public User GetResult()
{
return _parser.Get(text);
}
[Benchmark]
public User GetResult2()
{
return _parser2.Get(text);
}
[Benchmark]
public User Custom()
{
return _customParser.Get(text);
}
[Serializable]
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
///
/// 建造者模式
///
public class UserBuilder2
: IMemberBuilder
{
private readonly User _original = new();
///
/// 原始对象
///
public User Original
=> _original;
///
public User Build()
=> _original;
///
public void Save(string name, TMember value)
{
switch (name)
{
case nameof(User.Id):
if (value is int idValue)
_original.Id = idValue;
break;
case nameof(User.Name):
if (value is string nameValue)
_original.Name = nameValue;
break;
case nameof(User.Age):
if (value is int ageValue)
_original.Age = ageValue;
break;
default:
break;
}
}
///
/// 工厂模式
///
public static readonly ICreator Creater = new DefaultCreater();
}
public class UserParser(HandXml xml)
: EntityParser(xml, UserBuilder2.Creater, null, true)
{
#region 配置
private readonly IXmlParser _id = xml.Content();
private readonly ContentReader _name = xml.Content();
private readonly IXmlParser _age = xml.Content();
#endregion
///
public override void ReadAttributes(IMemberStore entity, XmlReader reader) { }
///
public override void ReadItem(IMemberBuilder entity, XmlReader reader, string name)
{
if (entity is UserBuilder2 builder)
ReadItem(builder.Original, reader, name);
else
base.ReadItem(entity, reader, name);
}
///
/// 使用自定义构造器
///
///
///
///
public void ReadItem(User entity, XmlReader reader, string name)
{
switch (name)
{
case nameof(User.Id):
if (_id.TryParse(reader, out var idResult))
entity.Id = idResult;
break;
case nameof(User.Name):
if (_name.TryParse(reader, out var nameResult))
entity.Name = nameResult;
break;
case nameof(User.Age):
if (_age.TryParse(reader, out var ageResult))
entity.Age = ageResult;
break;
}
}
}
1.3 性能分析
ParseXml开箱即用性能就比XmlSerializer好不少
通过定制对象构建组件或扩展重写能进一步提高性能
定制对象构建组件和扩展重写解析器可以通过SG来生成,之后会开发相应的源码生成器通用性对比
2.1 XmlSerializer通过Attribute标记映射
通过XmlRoot、XmlAttribute、XmlElement和XmlText等来映射属性
一个类只能支持一种格式的xml文件
[XmlRoot(“Root”)]
public class Person
{
[XmlAttribute(“PersonId”)]
public int Id { get; set; }
[XmlElement(“PersonName”)]
public string Name { get; set; }private static readonly XmlSerializer _serializer = new(typeof(Person));
[Fact]
public void Deserialize()
{
var text = @“<?xml version=""1.0"" encoding=""utf-8""?>
<Root PersonId=”“1"”>
张三
“;
using var stringReader = new StringReader(text);
Person? person = _serializer.Deserialize(stringReader) as Person;
Assert.NotNull(person);
Assert.Equal(1, person.Id);
Assert.Equal(“张三”, person.Name);
}
[Fact]
public void Throw()
{
var text = @”<?xml version=""1.0"" encoding=""utf-8""?>
1
张三
“;
using var stringReader = new StringReader(text);
Assert.ThrowsAny(() => _serializer.Deserialize(stringReader));
}
}
2.2 ParseXml通过Fluent API配置
WithItem配置节点
WithAttribute配置属性
Entity支持可选参数配置当前标签映射
映射的成员可以是属性也可以是字段和构造函数的参数
配置的解析Xml对象可以定义为静态或注入IOC容器,该对象解析Xml不产生状态支持多线程同时调用(线程安全)
同一个类可以配置多个解析对象用来把不同格式的Xml转化为相同类型的对象
public record User(int Id, string Name);
.
[Fact]
public void WithNode()
{
var id = 123;
var name = “Jxj”;
var text = @$”<?xml version=""1.0"" encoding=""utf-8""?>
{id}
{name}
";var userParser = HandXml.Default.Entity<User>() .WithItem<int>(nameof(User.Id)) .WithItem(nameof(User.Name)); User result = userParser.Get(text); Assert.NotNull(result); Assert.Equal(id, result.Id); Assert.Equal(name, result.Name);}
[Fact]
public void WithAttribute()
{
var id = 123;
var name = “Jxj”;
var text = @$“<?xml version=""1.0"" encoding=""utf-8""?>
<User Id=”“{id}”" Name=“”{name}“” />";var userParser = HandXml.Default.Entity<User>() .WithAttribute<int>(nameof(User.Id)) .WithAttribute(nameof(User.Name)) .First("User"); User result = userParser.Parse(text); Assert.NotNull(result); Assert.Equal(id, result.Id); Assert.Equal(name, result.Name);}
[Fact]
public void Content()
{
var id = 123;
var name = “Jxj”;
var text = @$“<?xml version=""1.0"" encoding=""utf-8""?>
<User Id=”“{id}”“>{name}”;var userParser = HandXml.Default.Entity<User>(nameof(User.Name)) .WithAttribute<int>(nameof(User.Id)) .First("User"); User result = userParser.Parse(text); Assert.NotNull(result); Assert.Equal(id, result.Id); Assert.Equal(name, result.Name);}
2.3 通用性分析
特性 XmlSerializer ParseXml
同类Xml格式 只支持一种格式 支持多种
映射对象 只映射属性 映射属性、字段和构造函数参数
映射Xml结构 支持属性、子节点和当前节点 支持属性、子节点和当前节点
结果类型 object需要转化 当前类型,直接使用
二、ParseXml主要功能读取单个节点
1.1 读取原始值
使用First方法读取单个节点原始值
var summaryReader = HandXml.Default.First(“summary”);
string summary = summaryReader.Parse(xmlReader);
1.2 读取强类型值
使用First泛型方法读取单个节点强类型值
var idParser = HandXml.Default.First(“Id”);
int id = idParser.Parse(xmlReader);读取单个属性
自增列
2.1 读取原始值
使用Attribute方法读取属性原始值
支持使用属性名或属性索引
var text = @"<member name = ““F:GenerateConvertTests.Supports.ColumnType.Identity””>
";
var nameReader = HandXml.Default.Attribute(“name”)
.First();
string name = nameReader.Parse(text);
2.2 读取强类型值
使用Attribute泛型方法读取属性强类型值
支持使用属性名或属性索引
var text = @$“<?xml version=""1.0"" encoding=""utf-8""?>
<User Id=”“123"”>Jxj";
var idReader = HandXml.Default.Attribute(0)
.Element(“User”)
.First();
int result = idReader.Parse(text);
3. 读取单个节点名
使用Name方法读取节点名
var text = @“<?xml version=""1.0"" encoding=""utf-8""?>
1
”;
var eachParser = HandXml.Default.Name()
.First();
var result = eachParser.Parse(text);
4. 解析到实体
xml反序列化
4.1 从子节点解析
Entity泛型方法指定实体类型
WithItem方法读取子节点原始值并绑到实体
WithItem泛型方法读取子节点强类型值并绑到实体
var text = @“<?xml version=""1.0"" encoding=""utf-8""?>
123
Jxj
20
”;
// Id、Name和Age节点转化为实体User的属性
var userParser = HandXml.Default.Entity()
.WithItem(nameof(User.Id))
.WithItem(nameof(User.Name))
.WithItem(nameof(User.Age));
User result = userParser.Get(text);
4.2 从属性解析
Entity泛型方法指定实体类型
WithAttribute方法读取属性原始值并绑到实体
WithAttribute泛型方法读取属性强类型值并绑到实体
var text = @$“<?xml version=""1.0"" encoding=""utf-8""?>
<User Id=”“123"” Name=““Jxj”” Age=““20"” />”;
// 第一个User节点的属性转化为实体User的属性
var userParser = HandXml.Default.Entity()
.WithAttribute(nameof(User.Id))
.WithAttribute(nameof(User.Name))
.WithAttribute(nameof(User.Age))
.Element(nameof(User))
.First();
User result = userParser.Parse(text);
4.3 从本节点解析原始值
Entity可选参数contentName指定本节点原始值绑定实体的成员名
var text = @$“<?xml version=""1.0"" encoding=""utf-8""?>
<User Id=”“123"” Age=““age””>Jxj";
var config = HandXml.Default;
// 第一个User节点的属性转化为实体User的属性,User节点的文本转化为实体User的Name
var userParser = config.Entity(nameof(User.Name))
.WithAttribute(nameof(User.Id))
.WithAttribute(nameof(User.Age))
.Element(nameof(User))
.First();
User result = userParser.Parse(text);
5. 解析集合
通过Each方法定义集合解析
var text = @“<?xml version=""1.0"" encoding=""utf-8""?>
1
张三
2
李四
3
<![CDATA[王二]]>
”;
var config = HandXml.Default;
// 每一个User节点转化为实体User
var repeatReader = config.Entity()
.WithItem(nameof(User.Id))
.WithItem(nameof(User.Name))
.Element(nameof(User))
.Each();
User[] result = repeatReader.Get(text)
.ToArray();
6. 解析字典
6.1 把列表解析为字典
使用Dictionary扩展方法解析字典,acceptDefault可选参数配置是否包含默认值
先定义键的解析规则
以子节点Id为键,子节点Name为值
var text = @“<?xml version=""1.0"" encoding=""utf-8""?>
1
张三
2
3
<![CDATA[王二]]>
”;
var config = HandXml.Default;
var dictionaryReader = config.Element(nameof(User.Id))
.Dictionary(config.Element(“Name”).First());
IDictionary<int,string> result = dictionaryReader.Get(text);
6.2 把子节点解析给字典
通过Name获取节点名
使用Dictionary扩展方法解析字典,acceptDefault可选参数配置是否包含默认值
var text = @“<?xml version=""1.0"" encoding=""utf-8""?>
1
张三
10
”;
var config = HandXml.Default;
var dictionaryReader = config.Name()
.Dictionary(config.Content().First())
// 跳过当前User节点
.MoveIn()
.Element(“User”)
.First();
IDictionary<string,string> result = dictionaryReader.Parse(text);
7. 包装类型转化
Convert方法转入一个转化器可以变成另一个类型的解析器
var text = @$“<?xml version=""1.0"" encoding=""utf-8""?>
<User Id=”“123"”>Jxj";
// 第一个User节点的Id属性转化为UserId
var idReader = HandXml.Default.Attribute(“Id”)
.Element(nameof(User))
.First()
.Convert(id => new UserId(id));
UserId result = idReader.Parse(text);
8. 复杂类型
通过WithItem可以一个解析器绑定到主解析器的成员上
通过WithEache可以把集合解析器绑定到主解析器的成员上
8.1 复杂类型的Case
using var fs = new FileStream(“rss.xml”, FileMode.Open, FileAccess.Read);
using var xmlReader = XmlReader.Create(fs);
var config = HandXml.Default;
// url和title节点分别转化为RssImage实体的Url和Title
var imageParser = config.Entity()
.WithItem(“url”, nameof(RssImage.Url))
.WithItem(“title”, nameof(RssImage.Title));
// 每一个item节点的title和link分别转化为RssItem实体的Title和Link
var itemParser = config.Entity()
.WithItem(“title”, nameof(RssItem.Title))
.WithItem(“link”, nameof(RssItem.Link))
.Element(“item”)
.Each();
var rssParser = config.Entity()
// title节点转化为Rss实体的Title
.WithItem(“title”, nameof(Rss.Title))
// image节点交给imageParser处理,处理的结果转化为Rss实体的Image
.WithItem(imageParser, “image”, nameof(Rss.Image))
// item节点交给itemParser处理,处理的结果转化为Rss实体的Items
.With(itemParser, “item”, nameof(Rss.Items));
Rss rss = rssParser.Get(xmlReader);
8.2 支持绑定子解析器的WithItem重载方法
item为子解析器
node为调用该解析器的子节点名
member为子解析器解析结果绑定实体成员名
TItem为子解析器解析结果和实体成员共同的类型
如果类型不一致结果会抛异常或被抛弃
EntityParser WithItem(IParser<XmlReader, TItem> item, string node, string member);
三、ParseXml的模块化
- HandXml是ParseXml的配置基座
HandXml包含Builders、Converters和DefaultValues
Builders、Converters和DefaultValues参与到解析Xml的各个过程内
通过Builders、Converters和DefaultValues配置可以调整Xml解析的结果和性能
class HandXml
{
///
/// 构造器提供者
///
IMemberBuilderProvider Builders { get; }
///
/// 转化器
///
XmlConvertCacher Converters { get; }
///
/// 默认值提供者
///
DefaultValueBuilder DefaultValues { get; }
}
1.1 Builders负责对象构建
默认使用反射构建
支持配置
///
/// 注册工厂
///
///
///
///
///
///
TProvider UseCreator<TProvider, TEntity>(this TProvider provider, ICreator<IMemberBuilder> creator);
///
/// 注册工厂
///
///
///
///
///
///
TProvider UseCreator<TProvider, TEntity>(this TProvider provider, Func<IMemberBuilder> func);
1.2 Converters负责xml节点原始处理
默认使用XmlConvert处理
默认支持18种基础类型(bool、byte、short、char、sbyte、ushort、int、uint、long、ulong、float、double、decimal、DateTime、string、Guid、DateTimeOffset和TimeSpan等)
支持配置
///
/// 配置转化器
///
///
///
///
HandXml Use<TValue, TConverter>(TConverter converter);
///
/// 配置转化器
///
///
///
HandXml Use(Converter<string, TValue> converter);
1.3 DefaultValues负责默认值
各类型的默认值默认是default
支持配置
///
/// 设置默认值
///
///
///
///
DefaultValueBuilder Use(TValue value);
四、ParseXml的组件化 - Xml解析接口IXmlParser
对Xml单节点、属性、重复节点或者复杂节点的解析类都实现了IXmlParser
///
/// Xml解析接口
///
///
interface IXmlParser
{
///
/// 解析
///
///
///
///
bool TryParse(XmlReader reader, out TResult result);
} - IXmlParser的主要实现类
AttributeReader和ContentReader是基础实现
其他类都是通过组装IXmlParser实例来实现
通过完美的组件化可以模拟和解析复杂的Xml文件
类 作用
AttributeReader 读取属性的原始文本
ContentReader 读取节点的原始文本
PrimitiveReader 调用AttributeReader或ContentReader,使用Converters把文本转化为需要的类型
ConvertParser<TSource, TDest> 把一种类型的解析器封装为另一种类型的解析器
FirstReader 解析碰到的第一个该节点
EachReader 解析连续重复的该节点
DictionaryParser<TKey, TValue> 解析为字典
EntityParser 解析包含多个属性或子节点为实体类型的成员
class PrimitiveReader(IXmlParser original, IConverter<string, TPrimitive> converter, TPrimitive defaultValue);
class ConvertParser<TSource, TDest>(HandXml xml, IXmlParser original, IConverter<TSource, TDest> converter, TDest defaultValue);
class FirstReader(string element, IXmlParser original, TResult defaultValue);
class RepeatReader(HandXml xml, string name, IXmlParser item);
class DictionaryParser<TKey, TValue>(IParser<XmlReader, TKey> key, IParser<XmlReader, TValue> value, IEqualityComparer comparer, bool acceptDefault);
class EntityParser(HandXml xml, ICreator<IMemberBuilder> creator, IMemberParser? content, bool hasItem = false);
五、ParseXml的可扩展性 - EntityParser的扩展性
EntityParser可以用来处理复杂Xml和复杂类型
可扩展性在处理复杂问题时可简化配置或提供性能 - UserParser扩展EntityParser的示例
UserParser封装了对象解析器,提高性能
UserParser封装了节点配置,简化使用
重写ReadAttributes跳过属性解析提高性能
重写ReadItem,使用switch代替字典查找提高性能
public class UserParser(HandXml xml)
: EntityParser(xml, UserBuilder2.Creater, null, true)
{
#region 配置
private readonly IXmlParser _id = xml.Content();
private readonly ContentReader _name = xml.Content();
private readonly IXmlParser _age = xml.Content();
#endregion
///
public override void ReadAttributes(IMemberStore entity, XmlReader reader) { }
///
public override void ReadItem(IMemberBuilder entity, XmlReader reader, string name)
{
if (entity is UserBuilder2 builder)
ReadItem(builder.Original, reader, name);
else
base.ReadItem(entity, reader, name);
}
///
/// 使用自定义构造器
///
///
///
///
public void ReadItem(User entity, XmlReader reader, string name)
{
switch (name)
{
case nameof(User.Id):
if (_id.TryParse(reader, out var idResult))
entity.Id = idResult;
break;
case nameof(User.Name):
if (_name.TryParse(reader, out var nameResult))
entity.Name = nameResult;
break;
case nameof(User.Age):
if (_age.TryParse(reader, out var ageResult))
entity.Age = ageResult;
break;
}
}
}