在(反)序列化树中使用自己的 XmlSerializer 将已知类型的列表传递给对象
本文关键字:类型 列表 对象 XmlSerializer 序列化 自己的 | 更新日期: 2023-09-27 18:35:25
我在Microsoft .net 3.5(VS2008)上使用C# 3。我在反序列化方面遇到问题。我在想要序列化的类层次结构中使用 DataContract 和 DataMember。
但是,我在一个容器中也有多态性,因此我需要将已知类型的列表传递给序列化程序。我的收藏是我在网上找到的可序列化词典:
[Serializable]
[XmlRoot("dictionary")]
public class SerializableSortedDictionary<TKey, TVal>
: SortedDictionary<TKey, TVal>, IXmlSerializable
{
#region Constants
private const string DictionaryNodeName = "Dictionary";
private const string ItemNodeName = "Item";
private const string KeyNodeName = "Key";
private const string ValueNodeName = "Value";
#endregion
#region Constructors
public SerializableSortedDictionary()
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary)
: base(dictionary)
{
}
public SerializableSortedDictionary(IComparer<TKey> comparer)
: base(comparer)
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary, IComparer<TKey> comparer)
: base(dictionary, comparer)
{
}
#endregion
#region IXmlSerializable Members
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
//writer.WriteStartElement(DictionaryNodeName);
foreach (KeyValuePair<TKey, TVal> kvp in this)
{
writer.WriteStartElement(ItemNodeName);
writer.WriteStartElement(KeyNodeName);
KeySerializer.Serialize(writer, kvp.Key);
writer.WriteEndElement();
writer.WriteStartElement(ValueNodeName);
ValueSerializer.Serialize(writer, kvp.Value);
writer.WriteEndElement();
writer.WriteEndElement();
}
//writer.WriteEndElement();
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
if (reader.IsEmptyElement)
{
return;
}
// Move past container
if (!reader.Read())
{
throw new XmlException("Error in Deserialization of Dictionary");
}
//reader.ReadStartElement(DictionaryNodeName);
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.ReadStartElement(ItemNodeName);
reader.ReadStartElement(KeyNodeName);
TKey key = (TKey)KeySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement(ValueNodeName);
TVal value = (TVal)ValueSerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadEndElement();
this.Add(key, value);
reader.MoveToContent();
}
//reader.ReadEndElement();
reader.ReadEndElement(); // Read End Element to close Read of containing node
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
// for serialization/deserialization pruporses
public void SetKnownTypes(Type[] extraTypes)
{
this.extraTypes = extraTypes;
}
public Type[] extraTypes = null;
#endregion
#region Private Properties
protected XmlSerializer ValueSerializer
{
get
{
if (valueSerializer == null)
{
if (extraTypes == null)
valueSerializer = new XmlSerializer(typeof(TVal));
else
valueSerializer = new XmlSerializer(typeof(TVal), extraTypes);
}
return valueSerializer;
}
}
private XmlSerializer KeySerializer
{
get
{
if (keySerializer == null)
{
if (extraTypes == null)
keySerializer = new XmlSerializer(typeof(TKey));
else
keySerializer = new XmlSerializer(typeof(TKey), extraTypes);
}
return keySerializer;
}
}
#endregion
#region Private Members
[NonSerialized]
private XmlSerializer keySerializer = null;
[NonSerialized]
private XmlSerializer valueSerializer = null;
#endregion
}
这是在其 TVal 中保存多态对象树的那个。所以你看到我已经修改了原始代码以添加已知类型的列表,这适用于序列化,因为我在我的高级类构造函数中设置了这个列表。(保存字典实例的类)。
此已知类型列表恰好在运行时使用此函数发现:
static public class TypeDiscoverer
{
public enum EFilter { All, OnlyConcreteTypes }
public enum EAssemblyRange { AllAppDomain, OnlyAssemblyOfRequestedType }
public static List<Type> FindAllDerivedTypes<T>(EFilter typesFilter, EAssemblyRange assembRange)
{
HashSet< Type > founds = new HashSet<Type>();
Assembly[] searchDomain =
assembRange == EAssemblyRange.OnlyAssemblyOfRequestedType ?
new Assembly[1] { Assembly.GetAssembly(typeof(T)) }
: AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in searchDomain)
{
founds = new HashSet<Type>(founds.Concat(FindAllDerivedTypes<T>(a, typesFilter)));
}
return founds.ToList();
}
public static List<Type> FindAllDerivedTypes<T>(Assembly assembly, EFilter typesFilter)
{
var derivedType = typeof(T);
List<Type> result = assembly
.GetTypes()
.Where(t =>
t != derivedType &&
derivedType.IsAssignableFrom(t)
).ToList();
if (typesFilter == EFilter.OnlyConcreteTypes)
result = result.Where(x => !x.IsAbstract).ToList();
return result;
}
}
这个动态系统允许我通过知道基类来发现已知类型。我一直想知道为什么框架不提供此功能......但是好吧..
所以我的问题是,我的可序列化字典是一个实用程序类,我不能专门化它来硬编码已知类型的列表,更不用说了,因为它是在运行时发现的。反序列化适用于未初始化的对象,因此我无法向字典反序列化程序提供已知类型的列表。
当然,目前,我将通过在字典中直接在 TVal 上使用我的 FindAllDerivedTypes
函数发现已知类型的列表来解决这个问题。
但是由于它的可扩展性不如永久提供的类型列表,我想知道是否有人可以为我提供真正的修复程序。
多谢。
您可以使用自定义 XmlReader(或在某些静态/线程本地存储变量中传递类型)。Ide一个例子
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace DynaXmlSer {
public class KnownTypesXmlReader: XmlTextReader {
public KnownTypesXmlReader(Stream ios): base(ios) {}
public Type[] ExtraTypes = null;
}
public partial class SerializableSortedDictionary<TKey, TVal>
: SortedDictionary<TKey, TVal>, IXmlSerializable
{
public void SetKnownTypes(Type[] extraTypes) {
this.extraTypes = extraTypes;
valueSerializer = null;
keySerializer = null;
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) {
if (reader.IsEmptyElement)
return;
if (!reader.Read())
throw new XmlException("Error in Deserialization of Dictionary");
//HERE IS THE TRICK
if (reader is KnownTypesXmlReader)
SetKnownTypes(((KnownTypesXmlReader)reader).ExtraTypes);
//reader.ReadStartElement(DictionaryNodeName);
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.ReadStartElement(ItemNodeName);
reader.ReadStartElement(KeyNodeName);
TKey key = (TKey)KeySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement(ValueNodeName);
TVal value = (TVal)ValueSerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadEndElement();
this.Add(key, value);
reader.MoveToContent();
}
//reader.ReadEndElement();
reader.ReadEndElement(); // Read End Element to close Read of containing node
}
}
public class BasicElement {
private string name;
public string Name {
get { return name; }
set { name = value; } }
}
public class ElementOne: BasicElement {
private string one;
public string One {
get { return one; }
set { one = value; }
}
}
public class ElementTwo: BasicElement {
private string two;
public string Two {
get { return two; }
set { two = value; }
}
}
public class Program {
static void Main(string[] args) {
Type[] extraTypes = new Type[] { typeof(ElementOne), typeof(ElementTwo) };
SerializableSortedDictionary<string, BasicElement> dict = new SerializableSortedDictionary<string,BasicElement>();
dict.SetKnownTypes(extraTypes);
dict["foo"] = new ElementOne() { Name = "foo", One = "FOO" };
dict["bar"] = new ElementTwo() { Name = "bar", Two = "BAR" };
XmlSerializer ser = new XmlSerializer(typeof(SerializableSortedDictionary<string, BasicElement>));
MemoryStream mem = new MemoryStream();
ser.Serialize(mem, dict);
Console.WriteLine(Encoding.UTF8.GetString(mem.ToArray()));
mem.Position = 0;
using(XmlReader rd = new KnownTypesXmlReader(mem) { ExtraTypes = extraTypes })
dict = (SerializableSortedDictionary<string, BasicElement>)ser.Deserialize(rd);
foreach(KeyValuePair<string, BasicElement> e in dict) {
Console.Write("Key = {0}, Name = {1}", e.Key, e.Value.Name);
if(e.Value is ElementOne) Console.Write(", One = {0}", ((ElementOne)e.Value).One);
else if(e.Value is ElementTwo) Console.Write(", Two = {0}", ((ElementTwo)e.Value).Two);
Console.WriteLine(", Type = {0}", e.Value.GetType().Name);
}
}
}
[Serializable]
[XmlRoot("dictionary")]
public partial class SerializableSortedDictionary<TKey, TVal>
: SortedDictionary<TKey, TVal>, IXmlSerializable
{
#region Constants
private const string DictionaryNodeName = "Dictionary";
private const string ItemNodeName = "Item";
private const string KeyNodeName = "Key";
private const string ValueNodeName = "Value";
#endregion
#region Constructors
public SerializableSortedDictionary()
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary)
: base(dictionary)
{
}
public SerializableSortedDictionary(IComparer<TKey> comparer)
: base(comparer)
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary, IComparer<TKey> comparer)
: base(dictionary, comparer)
{
}
#endregion
#region IXmlSerializable Members
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
//writer.WriteStartElement(DictionaryNodeName);
foreach (KeyValuePair<TKey, TVal> kvp in this)
{
writer.WriteStartElement(ItemNodeName);
writer.WriteStartElement(KeyNodeName);
KeySerializer.Serialize(writer, kvp.Key);
writer.WriteEndElement();
writer.WriteStartElement(ValueNodeName);
ValueSerializer.Serialize(writer, kvp.Value);
writer.WriteEndElement();
writer.WriteEndElement();
}
//writer.WriteEndElement();
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
public Type[] extraTypes = null;
#endregion
#region Private Properties
protected XmlSerializer ValueSerializer
{
get
{
if (valueSerializer == null)
{
if (extraTypes == null)
valueSerializer = new XmlSerializer(typeof(TVal));
else
valueSerializer = new XmlSerializer(typeof(TVal), extraTypes);
}
return valueSerializer;
}
}
private XmlSerializer KeySerializer
{
get
{
if (keySerializer == null)
{
if (extraTypes == null)
keySerializer = new XmlSerializer(typeof(TKey));
else
keySerializer = new XmlSerializer(typeof(TKey), extraTypes);
}
return keySerializer;
}
}
#endregion
#region Private Members
[NonSerialized]
private XmlSerializer keySerializer = null;
[NonSerialized]
private XmlSerializer valueSerializer = null;
#endregion
}
}
输出:
键 = 条形,名称 = 条形图,两个 = BAR,类型 = 元素二键 = foo, 名称 = foo, 一 = FOO, 类型 = 元素一
您可以注释掉类型的传递,反序列化失败:using(XmlReader rd = new KnownTypesXmlReader(mem) /* { ExtraTypes = extraTypes } */)
添加的评论:
我正在按以下顺序寻找解决方案:
- 如果可以的话,使用已经存在的东西(
[XmlInclude]
)。(运行时约束不允许这样做。 - 自定义一些涉及的类 - 问题出在
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
因此XmlReader
是完美的候选者。我建议使用iterface
作为最终解决方案(例如interface KnownTypes { Type[] GetKnownTypes(object me, string hint, params Type[] involved); }
) - 如果上述失败(如果您无法控制反序列化程序),请使用
static
变量(或用于多个线程的线程本地存储,或静态同步(弱)字典)进行其他配置。(不完美,似乎您可以使用选项 2。
首先XmlSerializer
忽略[Serializable],
[NonSerialized]
、[DataContract]
和[DataMember]
属性 - 它由IXmlSerializable
接口控制(它完全改变了对象序列化的行为),或者默认情况下它序列化对象的所有公共成员/属性,您可以通过 [XmlRoot]
等属性向XmlSerializer
提供提示, [XmlAttribute]
、[XmlIgnore]
[XmlArray]
、[XmlElement]
、[XmlArrayItem]
、[XmlInclude]
、[XmlText]
等。
您所追求的功能已包含在这些属性中。假设您有SerializableSortedDictionary<string, Car>
其中Car
是具有子类Volvo
和Audi
的类。
[XmlInclude(typeof(Audi))]
[XmlInclude(typeof(Volvo))]
public class Car {
private string m_Name = "Car";
public virtual string Name {
get { return m_Name; }
set { m_Name = value; }
}
}
public class Audi : Car {
private string m_Name = "Audi";
public override string Name {
get { return m_Name; }
set { m_Name = value; }
}
}
public class Volvo : Car {
private string m_Name = "Volvo";
public override string Name {
get { return m_Name; }
set { m_Name = value; }
}
}
您所需要的只是通过XmlInclude
用所有可能的子类装饰基类
var dic = new SerializableSortedDictionary<string, Car>();
dic.Add("0", new Car());
dic.Add("1", new Audi());
dic.Add("2", new Volvo());
var serializer = new XmlSerializer(typeof(SerializableSortedDictionary<string, Car>));
var builder = new StringBuilder();
using(var writer = new StringWriter(builder)) {
serializer.Serialize(writer, dic);
}
反序列化也有效。您可能会注意到生成的 xml 中的 xsi:type 属性 - xml 序列化程序如何保存有关类型的信息。如果不指定序列化对象,基本上不可能从序列化对象中"猜测"类型。它不适用于未通过XmlInclude
指定的泛型类型 - 这是一项安全功能(如果攻击者可以在您解析 xml 源时让您创建他喜欢的任何对象的实例,您可能会遇到严重的麻烦)。