在(反)序列化树中使用自己的 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 函数发现已知类型的列表来解决这个问题。

但是由于它的可扩展性不如永久提供的类型列表,我想知道是否有人可以为我提供真正的修复程序。

多谢。

在(反)序列化树中使用自己的 XmlSerializer 将已知类型的列表传递给对象

您可以使用自定义 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 } */)

添加的评论:

我正在按以下顺序寻找解决方案:

  1. 如果可以的话,使用已经存在的东西([XmlInclude])。(运行时约束不允许这样做。
  2. 自定义一些涉及的类 - 问题出在void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)因此XmlReader是完美的候选者。我建议使用iterface作为最终解决方案(例如 interface KnownTypes { Type[] GetKnownTypes(object me, string hint, params Type[] involved); }
  3. 如果上述失败(如果您无法控制反序列化程序),请使用static变量(或用于多个线程的线程本地存储,或静态同步(弱)字典)进行其他配置。(不完美,似乎您可以使用选项 2。

首先XmlSerializer忽略[Serializable], [NonSerialized][DataContract][DataMember]属性 - 它由IXmlSerializable接口控制(它完全改变了对象序列化的行为),或者默认情况下它序列化对象的所有公共成员/属性,您可以通过 [XmlRoot] 等属性向XmlSerializer提供提示, [XmlAttribute][XmlIgnore] [XmlArray][XmlElement][XmlArrayItem][XmlInclude][XmlText]等。

您所追求的功能已包含在这些属性中。假设您有SerializableSortedDictionary<string, Car>其中Car是具有子类VolvoAudi的类。

[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 源时让您创建他喜欢的任何对象的实例,您可能会遇到严重的麻烦)。