WPF 核心型別(Brush、FontFamily、FontWeight、Color 等)未標記 [Serializable],預設 XmlSerializer 無法直接序列化,拋出例外。
繼承 Dictionary<string, TypeAndValue> 並實作 IXmlSerializable,搭配輔助類別 TypeAndValue 封裝型別與實例,利用 TypeConverter 進行字串轉換。
public class TypeAndValue
{
public Type Type { get; set; }
public object Value { get; set; }
public TypeAndValue() { }
public TypeAndValue(Type type, object value) { Type = type; Value = value; }
public override bool Equals(object obj)
{
var other = obj as TypeAndValue;
if (other == null) return false;
if (other.Type != this.Type) return false;
var eqMethod = Type.GetMethod("Equals", new[] { Type });
return (bool)eqMethod.Invoke(other.Value, new object[] { this.Value });
}
}
public class PropertySerializateDictionary : Dictionary<string, TypeAndValue>, IXmlSerializable
{
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader)
{
if (reader.IsEmptyElement) return;
reader.Read(); // 移至根元素
string typeName = reader.GetAttribute("Type");
var type = Type.GetType(typeName);
var props = type.GetProperties();
while (reader.NodeType != XmlNodeType.EndElement)
{
string key = reader.Name;
var instance = Activator.CreateInstance(type);
this[key] = new TypeAndValue(type, instance);
if (!reader.IsEmptyElement)
{
reader.ReadStartElement();
if (reader.NodeType != XmlNodeType.EndElement)
{
foreach (var pi in props)
{
string raw = reader.ReadElementString(pi.Name);
object converted = ConvertValue(pi.PropertyType, raw);
pi.SetValue(instance, converted, null);
}
}
}
reader.Read();
}
}
public void WriteXml(XmlWriter writer)
{
if (Count == 0) return;
foreach (var kvp in this)
{
writer.WriteStartElement(kvp.Key);
writer.WriteAttributeString("Type", kvp.Value.Type.FullName);
foreach (var pi in kvp.Value.Type.GetProperties())
{
writer.WriteElementString(pi.Name, pi.GetValue(kvp.Value.Value, null)?.ToString());
}
writer.WriteEndElement();
}
}
private object ConvertValue(Type targetType, string input)
{
if (targetType == typeof(Brush))
return new BrushConverter().ConvertFromString(input);
if (targetType == typeof(FontFamily))
return new FontFamilyConverter().ConvertFromString(input);
if (targetType == typeof(FontWeight))
return new FontWeightConverter().ConvertFromString(input);
if (targetType == typeof(Color))
return (Color)ColorConverter.ConvertFromString(input);
return Convert.ChangeType(input, targetType);
}
public override bool Equals(object obj) => /* 比較每個 Key/Value */ true;
}
var dict = new PropertySerializateDictionary();
dict["TitleStyle"] = new TypeAndValue(typeof(SerialMock), new SerialMock
{
FontWeight = FontWeights.Bold,
FontFamily = new FontFamily("Segoe UI")
});
// 序列化
var serializer = new XmlSerializer(typeof(PropertySerializateDictionary));
using (var ms = new MemoryStream())
{
serializer.Serialize(ms, dict);
ms.Position = 0;
var xml = Encoding.UTF8.GetString(ms.ToArray());
Console.WriteLine(xml);
}
// 反序列化
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
{
var restored = (PropertySerializateDictionary)serializer.Deserialize(ms);
// restored["TitleStyle"].Value 恢復 FontWeight、FontFamily
}
[TestMethod]
public void ReadXmlTest()
{
var dict = new PropertySerializateDictionary();
dict["People1"] = new TypeAndValue(typeof(SerialMock), new SerialMock { FirstName = "a", FontWeight = FontWeights.Light, BirthDate = new DateTime(1984,5,17) });
dict["People2"] = new TypeAndValue(typeof(SerialMock), new SerialMock { FirstName = "b", FontWeight = FontWeights.Black, FontFamily = new FontFamily("PMingLiU") });
var serializer = new XmlSerializer(typeof(PropertySerializateDictionary));
using (var ms = new MemoryStream())
{
serializer.Serialize(ms, dict);
ms.Position = 0;
var restored = (PropertySerializateDictionary)serializer.Deserialize(ms);
Assert.AreEqual(dict, restored); // 需正確實作 Equals
}
}
ConvertValue 邏輯(如 JSON 序列化後存字串)。DataContractSerializer 或 JSON.NET,效能更佳。