xml的序列化
序列化
使用c#的xml序列化类,可以实现对一个对象的序列化和反序列化。
XmlSerializer xml = new XmlSerializer(typeof(Fight));
Fight fight = new Fight() { hp = 60, atk = 30 };
xml.Serialize(File.Open("D:\\666.xml", FileMode.Create), fight);
存在自定义类Fight
public class Fight { public int hp; public int atk; }
此操作会在D盘生成一个名叫666.xml的文件。
打开后内容应该类似于
<?xml version="1.0" encoding="utf-8"?>
<Fight xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<hp>60</hp>
<atk>30</atk>
</Fight>
序列化会查看这个类的所有属性,依次将这些数据以文字形式保存。
例如string,int,bool,时间类等基础类型,都有从文字转化而来的方法。
但图像(比如unity中的精灵),则不行。
如果一种数据集,存在以int类型为参数的索引器,那么这个字段或属性也可以序列化。
例如数组,List集合。字典,队列,栈这种数据类型则不行。
反序列化
XmlSerializer xml = new XmlSerializer(typeof(Fight));
Fight fight = (Fight)xml.Deserialize(File.OpenRead("X:\\666.xml"));
Console.WriteLine(fight.hp);
Console.WriteLine(fight.atk);
Deserialize方法可以读取xml文件,并分析其中的文字,转换为对应的类型。最后手动赋值。
这个过程和你手动赋值一样,所以序列化和反序列化操作,只会针对public的字段或属性。
但数组,集合等除外,他们是引用类型,只要可以读取,不需要写入权限。
此外,他会调用这个类型的公共无参构造器,他也要求序列化的类必须有一个公共无参构造器。
构造一个xml树
如果需要序列化多个对象,并把他们装在同一个文件中,那就需要手动的把他们装在一起。
XElement element = new XElement("Save");
element.Add(new XElement("Atk", 60));
element.Add(new XElement("Def", 120));
element.Save("D:\\666.xml");
此操作新建了一个xml对象,并添加了一些内容。最后保存在D盘的666.xml文件中
打开后内容应该类似于
<?xml version="1.0" encoding="utf-8"?>
<Save>
<Atk>60</Atk>
<Def>120</Def>
</Save>
读取:
XElement element = XElement.Load("D:\\666.xml");
Console.WriteLine(element.Element("Atk").Value);
Console.WriteLine(element.Element("Def").Value);
用xml树保存多个序列化内容
将序列化内容添加至xml树中
我们只需要把这个“手动添加的内容”改成序列化出来的东西,即可保存多个对象了。
但是xml序列化没有生成字符串的方式,只能写入到流中。
幸运的是,c#有一个字符串流,可以伪装成流。并将写入的东西以字符串形式输出。
过程如下
XmlSerializer xml = new XmlSerializer(typeof(Fight));
Fight fight = new Fight() { hp = 315, atk = 90 };
StringWriter sw = new StringWriter();
xml.Serialize(sw, fight);
XElement xel = XElement.Parse(sw.ToString());
XElement element = new XElement("Save");
element.Add(xel);
element.Save("D:\\666.xml");
生成的xml文件
<?xml version="1.0" encoding="utf-8"?>
<Save>
<Fight xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<hp>315</hp>
<atk>90</atk>
</Fight>
</Save>
从xml树中序列化需要的内容
xml的反序列化也不支持从字符串直接读取。我们使用类似的方式先声明一个伪装的流。
XmlSerializer xml = new XmlSerializer(typeof(Fight));
XElement element = XElement.Load("D:\\666.xml");
StringReader sr = new StringReader(element.Element("Fight").ToString());
Fight fight = (Fight)xml.Deserialize(sr);
示例:序列化一个字典
序列化对象,添加至xml树中
字典中有多个对象,下列代码演示如何手动构造一个xml树,并将所有字典中的序列化后的内容添加至xml树中。
Dictionary<string, Fight> dic = new Dictionary<string, Fight>();
dic.Add("史莱姆", new Fight() { atk = 1, hp = 10 });
dic.Add("小骷髅", new Fight() { atk = 3, hp = 6 });
dic.Add("爆虫", new Fight() { atk = 15, hp = 1 });
dic.Add("精英史莱姆", new Fight() { atk = 4, hp = 15 });
XmlSerializer xml_1 = new XmlSerializer(typeof(string));
XmlSerializer xml_2 = new XmlSerializer(typeof(Fight));
XElement element = new XElement("Save");
foreach (var item in dic)
{
XElement xel = new XElement("KV");
StringWriter sw1 = new StringWriter();
xml_1.Serialize(sw1, item.Key);
xel.Add(XElement.Parse(sw1.ToString()));
StringWriter sw2 = new StringWriter();
xml_2.Serialize(sw2, item.Value);
xel.Add(XElement.Parse(sw2.ToString()));
element.Add(xel);
}
element.Save("D:\\666.xml");
生成文件内容如下
<?xml version="1.0" encoding="utf-8"?>
<Save>
<KV>
<string>史莱姆</string>
<Fight xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<hp>10</hp>
<atk>1</atk>
</Fight>
</KV>
<KV>
<string>小骷髅</string>
<Fight xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<hp>6</hp>
<atk>3</atk>
</Fight>
</KV>
<KV>
<string>爆虫</string>
<Fight xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<hp>1</hp>
<atk>15</atk>
</Fight>
</KV>
<KV>
<string>精英史莱姆</string>
<Fight xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<hp>15</hp>
<atk>4</atk>
</Fight>
</KV>
</Save>
读取xml文件,并反序列化至对象
由于你是手动将他们序列化的,因此反序列化的过程也需要你自己写逻辑。
Dictionary<string, Fight> dic = new Dictionary<string, Fight>();
XmlSerializer xml_1 = new XmlSerializer(typeof(string));
XmlSerializer xml_2 = new XmlSerializer(typeof(Fight));
XElement element = XElement.Load("D:\\666.xml");
foreach (var item in element.Elements("KV"))
{
StringReader sr1 = new StringReader(item.Element("string").ToString());
string s = (string)xml_1.Deserialize(sr1);
StringReader sr2 = new StringReader(item.Element("Fight").ToString());
Fight f = (Fight)xml_2.Deserialize(sr2);
dic.Add(s, f);
}
Console.WriteLine(dic["史莱姆"].atk);
附录
存档特性
定义类
/// <summary>
/// 需要保存的属性,仅适用于静态可读写的属性。不区分是否公开。
/// 注意数据类型需要是可以序列化的。例如字典等类型不可用。
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
public class SaveAttribute : Attribute
{
#region 特性部分
/// <summary>
/// 序列化的顺序,某些数据极为复杂,不希望摆在开头位置时可调整。
/// 可以对类使用,控制类的顺序。
/// 如果没有该特性,视为值为0。
/// </summary>
public int Order = 0;
/// <summary>
/// 此属性的作用区间
/// 单次游戏使用的数据使用<see cref="SaveScope.Game"/>
/// <br/>全局数据(例如成就)使用<see cref="SaveScope.Option"/>
/// <br/>默认为单次游戏数据
/// </summary>
public SaveScope Scope = SaveScope.Game;
#endregion 特性部分
#region 私有字段部分
private PropertyInfo property;
private XmlSerializer xmlSerializer;
private string DeclaringFullName;
#endregion 私有字段部分
#region 公开方法部分
public XElement Serializer()
{
StringWriter writer = new StringWriter();
xmlSerializer.Serialize(writer, property.GetValue(null));
StringReader read = new StringReader(writer.ToString());
return new XElement("Property", new XAttribute("Name", property.Name), XElement.Load(read));
}
public void SetValue(XElement reader)
{
property.SetValue(null, xmlSerializer.Deserialize(XmlReader.Create(reader.FirstNode.CreateReader(), new XmlReaderSettings())));
}
#endregion 公开方法部分
#region 静态私有部分
static SaveAttribute()
{
var pro = Assembly.GetExecutingAssembly()
.GetTypes()
.SelectMany(t => t.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic))
.Select(p => (p.GetCustomAttribute<SaveAttribute>(), p))
.Where(p => p.Item1 != null && p.Item2.CanWrite && p.Item2.CanRead)
.ToArray();
var dic = pro.Select(p => p.Item2.PropertyType).Distinct().ToDictionary(d => d, d => new XmlSerializer(d));
properties = pro.Select(save =>
{
save.Item1.property = save.Item2;
save.Item1.xmlSerializer = dic[save.Item2.PropertyType];
save.Item1.DeclaringFullName = save.Item2.DeclaringType.FullName;
return save.Item1;
}).OrderBy(p => p.property.DeclaringType.GetCustomAttribute<SaveAttribute>()?.Order ?? 0)
.ThenBy(p => p.Order)
.ToLookup(p => p.DeclaringFullName);
}
private static ILookup<string, SaveAttribute> properties;
#endregion 静态私有部分
#region 静态公开部分
/// <summary>
/// 搜索带有<see cref="SaveAttribute"/>的静态属性序列化入指定路径
/// </summary>
/// <param name="path">文件保存路径</param>
public static XElement Write(string path = null, SaveScope scope = SaveScope.Game)
{
var xel = new XElement("Save", properties.Select(item =>
new XElement("Class", new XAttribute("FullName", item.Key), item
.Where(info => (info.Scope & scope) == info.Scope).Select(info => info.Serializer())))
.Where(ele => ele.Elements().Any()));
if (path != null)
xel.Save(path);
return xel;
}
/// <summary>
/// 解析序列化文件,反序列化至指定属性中
/// </summary>
/// <param name="path">文件保存路径</param>
public static void Read(string path)
{
foreach (var (a, b) in XElement.Load(path).Elements("Class")
.Join(properties, a => (string)a.Attribute("FullName"), b => b.Key, (a, b) => (a, b))
.SelectMany(c => c.a.Elements("Property")
.Join(c.b, a => a.Attribute("Name")?.Value, b => b.property.Name, (a, b) => (a, b))))
{
b.SetValue(a);
}
}
#endregion 静态公开部分
}
public enum SaveScope
{
Game = 1 << 0,
Option = 1 << 1
}
使用:
public class Map
{
[Save] public static string name { get; set; } = "妖精花园";
}
在静态属性上添加这个特性(无所谓是否为公开),他们会被标记。
调用他的静态方法,将所有标记的属性的值全部序列化。
SaveAttribute.Write("D:\\666.xml");
读取时,使用反序列化方法:
SaveAttribute.Read("D:\\666.xml");